mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 01:01:57 +00:00
perf: speed up G-code export by up to 7% via post-processing fixes (#16031)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
co-authored by
Rodrigo Faselli
parent
c67b54b39d
commit
6e0f04815b
@@ -871,19 +871,6 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Case-insensitive compare of a JSON key against a fixed ASCII one, without
|
||||
// boost::iequals, whose std::locale() takes a lock the whole process shares in the
|
||||
// MSVC runtime.
|
||||
static bool ascii_iequals(const std::string &key, const char *literal)
|
||||
{
|
||||
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
|
||||
size_t i = 0;
|
||||
for (; i < key.size() && literal[i] != '\0'; ++ i)
|
||||
if (lower(key[i]) != lower(literal[i]))
|
||||
return false;
|
||||
return i == key.size() && literal[i] == '\0';
|
||||
}
|
||||
|
||||
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
|
||||
{
|
||||
json j;
|
||||
|
||||
@@ -806,13 +806,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&changes](GCodeReader &parser, const GCodeReader::GCodeLine &line) {
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (boost::iequals(cmd, "M204") || boost::iequals(cmd, "M201") ||
|
||||
boost::iequals(cmd, "M202"))
|
||||
if (ascii_iequals(cmd, "M204") || ascii_iequals(cmd, "M201") ||
|
||||
ascii_iequals(cmd, "M202"))
|
||||
changes.acceleration = true;
|
||||
else if ((boost::iequals(cmd, "M205") || boost::iequals(cmd, "M207") || boost::iequals(cmd, "M566")) &&
|
||||
else if ((ascii_iequals(cmd, "M205") || ascii_iequals(cmd, "M207") || ascii_iequals(cmd, "M566")) &&
|
||||
custom_gcode_line_has_xy_parameter(line.raw()))
|
||||
changes.jerk = true;
|
||||
else if (boost::iequals(cmd, "SET_VELOCITY_LIMIT")) {
|
||||
else if (ascii_iequals(cmd, "SET_VELOCITY_LIMIT")) {
|
||||
changes.acceleration |= boost::icontains(line.raw(), "ACCEL=");
|
||||
changes.jerk |= boost::icontains(line.raw(), "SQUARE_CORNER_VELOCITY=");
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
|
||||
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
|
||||
m_g1_f_pattern(R"(G1 F([0-9]+))")
|
||||
{
|
||||
const size_t indices = std::max(m_config.adaptive_pressure_advance.size(), m_config.enable_pressure_advance.size());
|
||||
for (size_t i = 0; i < indices && !m_enabled; ++i)
|
||||
m_enabled = m_config.adaptive_pressure_advance.get_at(i) && m_config.enable_pressure_advance.get_at(i);
|
||||
}
|
||||
|
||||
// Method to get the interpolator for a specific filament config index.
|
||||
@@ -66,6 +69,12 @@ AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config
|
||||
* @return A string containing the processed G-code with adaptive pressure advance applied.
|
||||
*/
|
||||
std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
// Without PA_CHANGE tags the loop below would only terminate the layer's last line.
|
||||
if (!m_enabled && gcode.find("; PA_CHANGE") == std::string::npos) {
|
||||
if (!gcode.empty() && gcode.back() != '\n')
|
||||
gcode += '\n';
|
||||
return std::move(gcode);
|
||||
}
|
||||
std::istringstream stream(gcode);
|
||||
std::string line;
|
||||
std::ostringstream output;
|
||||
|
||||
@@ -78,6 +78,7 @@ private:
|
||||
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
|
||||
double m_current_feedrate; ///< Current, latest feedrate.
|
||||
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
|
||||
bool m_enabled{false}; ///< Whether any filament config index has both PA and adaptive PA on, the only ones that emit PA_CHANGE tags.
|
||||
|
||||
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
|
||||
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../GCode.hpp"
|
||||
#include "../LocalesUtils.hpp"
|
||||
#include "libslic3r/Extruder.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
@@ -408,13 +409,13 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
if (*c == 0 || *c == ';')
|
||||
break;
|
||||
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
//BBS: Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == 'E') ? 3 : (*c == 'F') ? 4 :
|
||||
(*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
new_pos[axis] = float(atof(++c));
|
||||
++ c;
|
||||
new_pos[axis] = float(atof_decimal_point(std::string_view(c, sline.data() + sline.size() - c)));
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
@@ -550,10 +551,9 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = CoolingLine::TYPE_G4;
|
||||
size_t pos_S = sline.find('S', 3);
|
||||
size_t pos_P = sline.find('P', 3);
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
line.time = line.time_max = float(
|
||||
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
|
||||
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||
(pos_S > 0) ? atof_decimal_point(sline.c_str() + pos_S + 1) :
|
||||
(pos_P > 0) ? atof_decimal_point(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
|
||||
}
|
||||
|
||||
@@ -909,8 +909,8 @@ public:
|
||||
{
|
||||
// Orca: find start pos by seaching G28/G29/PRINT_START/START_PRINT commands
|
||||
auto is_start_pos = [](const std::string& curr_cmd) {
|
||||
return boost::iequals(curr_cmd, "G28") || boost::iequals(curr_cmd, "G29") || boost::iequals(curr_cmd, "PRINT_START") ||
|
||||
boost::iequals(curr_cmd, "START_PRINT");
|
||||
return ascii_iequals(curr_cmd, "G28") || ascii_iequals(curr_cmd, "G29") || ascii_iequals(curr_cmd, "PRINT_START") ||
|
||||
ascii_iequals(curr_cmd, "START_PRINT");
|
||||
};
|
||||
assert(!m_lines.empty());
|
||||
const float time_step = backtrace.time_step();
|
||||
@@ -1265,6 +1265,9 @@ void GCodeProcessor::run_post_process()
|
||||
// Process inline placeholders (print_time_total_sec, print_time_day, print_time_hour, print_time_minute, print_time_sec and used_filament_length)
|
||||
auto process_inline_placeholders = [&](std::string& gcode_line) {
|
||||
bool processed = false;
|
||||
// Every inline placeholder contains '@', so a line without one has nothing to replace.
|
||||
if (gcode_line.find('@') == std::string::npos)
|
||||
return processed;
|
||||
|
||||
const std::string& print_time_total_placeholder = reserved_tag(ETags::Print_Time_Total_Sec_Placeholder);
|
||||
const std::string& print_time_day_placeholder = reserved_tag(ETags::Print_Time_Day_Placeholder);
|
||||
@@ -4007,13 +4010,13 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (m_flavor == gcfKlipper)
|
||||
{
|
||||
if (boost::iequals(cmd, "SET_VELOCITY_LIMIT"))
|
||||
if (ascii_iequals(cmd, "SET_VELOCITY_LIMIT"))
|
||||
{
|
||||
process_SET_VELOCITY_LIMIT(line);
|
||||
return;
|
||||
}
|
||||
// ORCA: Add Pressure Advance visualization support
|
||||
if (boost::iequals(cmd, "SET_PRESSURE_ADVANCE"))
|
||||
if (ascii_iequals(cmd, "SET_PRESSURE_ADVANCE"))
|
||||
{
|
||||
process_SET_PRESSURE_ADVANCE(line);
|
||||
return;
|
||||
@@ -7551,51 +7554,72 @@ void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_la
|
||||
actual_speed_moves = std::move(machine.actual_speed_moves);
|
||||
}
|
||||
|
||||
// insert actual speed moves into the move list
|
||||
unsigned int inserted_actual_speed_moves_count = 0;
|
||||
std::vector<GCodeProcessorResult::MoveVertex> new_moves;
|
||||
std::map<unsigned int, unsigned int> id_map;
|
||||
for (auto it = actual_speed_moves.begin(); it != actual_speed_moves.end(); ++it) {
|
||||
const unsigned int base_id = it->move_id + inserted_actual_speed_moves_count;
|
||||
if (it->position.has_value()) {
|
||||
// insert actual speed move into the move list
|
||||
// clone from existing move
|
||||
GCodeProcessorResult::MoveVertex new_move = result.moves[base_id];
|
||||
// override modified parameters
|
||||
new_move.time = { 0.0f, 0.0f };
|
||||
new_move.position = *it->position;
|
||||
new_move.actual_feedrate = it->actual_feedrate;
|
||||
new_move.delta_extruder = *it->delta_extruder;
|
||||
new_move.feedrate = *it->feedrate;
|
||||
new_move.width = *it->width;
|
||||
new_move.height = *it->height;
|
||||
new_move.mm3_per_mm = *it->mm3_per_mm;
|
||||
new_move.fan_speed = *it->fan_speed;
|
||||
new_move.temperature = *it->temperature;
|
||||
new_move.internal_only = true;
|
||||
new_moves.push_back(new_move);
|
||||
// actual_speed_moves holds, per block in move order, the moves to insert before the block's move and then an
|
||||
// entry without a position for that move; positioned entries after the last such entry are dropped.
|
||||
std::vector<GCodeProcessorResult::MoveVertex>& moves = result.moves;
|
||||
size_t inserted_actual_speed_moves_count = 0;
|
||||
size_t kept = 0;
|
||||
size_t group_start = 0;
|
||||
for (size_t i = 0; i < actual_speed_moves.size(); ++i) {
|
||||
if (actual_speed_moves[i].position.has_value())
|
||||
continue;
|
||||
const unsigned int move_id = actual_speed_moves[i].move_id;
|
||||
// A VG1 block has no move of its own, so its id can fall behind the previous block's or point past the list.
|
||||
if (move_id < moves.size() && (kept == 0 || move_id > actual_speed_moves[kept - 1].move_id)) {
|
||||
inserted_actual_speed_moves_count += i - group_start;
|
||||
moves[move_id].actual_feedrate = actual_speed_moves[i].actual_feedrate;
|
||||
// A seam vertex right after a block's move shares its actual speed.
|
||||
if (move_id + 1 < moves.size() && moves[move_id + 1].type == EMoveType::Seam)
|
||||
moves[move_id + 1].actual_feedrate = actual_speed_moves[i].actual_feedrate;
|
||||
for (size_t j = group_start; j <= i; ++j, ++kept)
|
||||
if (kept != j)
|
||||
actual_speed_moves[kept] = std::move(actual_speed_moves[j]);
|
||||
}
|
||||
else {
|
||||
result.moves.insert(result.moves.begin() + base_id, new_moves.begin(), new_moves.end());
|
||||
id_map[it->move_id] = base_id + new_moves.size();
|
||||
// update move actual speed
|
||||
result.moves[base_id + new_moves.size()].actual_feedrate = it->actual_feedrate;
|
||||
inserted_actual_speed_moves_count += new_moves.size();
|
||||
// synchronize seams actual speed
|
||||
if (base_id + new_moves.size() + 1 < result.moves.size()) {
|
||||
GCodeProcessorResult::MoveVertex& move = result.moves[base_id + new_moves.size() + 1];
|
||||
if (move.type == EMoveType::Seam)
|
||||
move.actual_feedrate = it->actual_feedrate;
|
||||
}
|
||||
new_moves.clear();
|
||||
group_start = i + 1;
|
||||
}
|
||||
actual_speed_moves.erase(actual_speed_moves.begin() + kept, actual_speed_moves.end());
|
||||
|
||||
// Walks the blocks back to front, so each shifted move is moved once, into its final slot.
|
||||
size_t read = moves.size(); // one past the last move not yet placed
|
||||
moves.resize(moves.size() + inserted_actual_speed_moves_count);
|
||||
size_t write = moves.size(); // one past the last free slot
|
||||
m_actual_speed_id_map.clear();
|
||||
size_t entry = actual_speed_moves.size();
|
||||
while (entry > 0) {
|
||||
const unsigned int block_id = actual_speed_moves[--entry].move_id;
|
||||
assert(block_id < read);
|
||||
while (read > block_id + 1)
|
||||
moves[--write] = moves[--read];
|
||||
const GCodeProcessorResult::MoveVertex block_move = moves[--read];
|
||||
moves[--write] = block_move;
|
||||
m_actual_speed_id_map.emplace_back(block_id, (unsigned int)write);
|
||||
for (; entry > 0 && actual_speed_moves[entry - 1].position.has_value(); --entry) {
|
||||
const TimeMachine::ActualSpeedMove& it = actual_speed_moves[entry - 1];
|
||||
GCodeProcessorResult::MoveVertex new_move = block_move;
|
||||
new_move.time = { 0.0f, 0.0f };
|
||||
new_move.position = *it.position;
|
||||
new_move.actual_feedrate = it.actual_feedrate;
|
||||
new_move.delta_extruder = *it.delta_extruder;
|
||||
new_move.feedrate = *it.feedrate;
|
||||
new_move.width = *it.width;
|
||||
new_move.height = *it.height;
|
||||
new_move.mm3_per_mm = *it.mm3_per_mm;
|
||||
new_move.fan_speed = *it.fan_speed;
|
||||
new_move.temperature = *it.temperature;
|
||||
new_move.internal_only = true;
|
||||
moves[--write] = new_move;
|
||||
}
|
||||
}
|
||||
assert(read == write);
|
||||
|
||||
// synchronize blocks' move_ids with after moves for actual speed insertion
|
||||
std::reverse(m_actual_speed_id_map.begin(), m_actual_speed_id_map.end());
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
for (GCodeProcessor::TimeBlock& block : m_time_processor.machines[i].blocks) {
|
||||
auto it = id_map.find(block.move_id);
|
||||
block.move_id = (it != id_map.end()) ? it->second : block.move_id + inserted_actual_speed_moves_count;
|
||||
auto it = std::lower_bound(m_actual_speed_id_map.begin(), m_actual_speed_id_map.end(), block.move_id,
|
||||
[](const std::pair<unsigned int, unsigned int>& entry, unsigned int id) { return entry.first < id; });
|
||||
block.move_id = (it != m_actual_speed_id_map.end() && it->first == block.move_id) ?
|
||||
it->second : block.move_id + (unsigned int)inserted_actual_speed_moves_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,54 +337,57 @@ class Print;
|
||||
|
||||
//BBS: add mutex for protection of gcode result
|
||||
mutable std::mutex result_mutex;
|
||||
GCodeProcessorResult& operator=(const GCodeProcessorResult &other)
|
||||
GCodeProcessorResult& operator=(const GCodeProcessorResult &other) { assign(other); return *this; }
|
||||
// Declared because the user-declared copy assignment suppresses the implicit move.
|
||||
GCodeProcessorResult& operator=(GCodeProcessorResult &&other) { assign(std::move(other)); return *this; }
|
||||
// Add a new member here, or neither assignment transfers it.
|
||||
template<class Other> void assign(Other &&other)
|
||||
{
|
||||
filename = other.filename;
|
||||
id = other.id;
|
||||
moves = other.moves;
|
||||
lines_ends = other.lines_ends;
|
||||
printable_area = other.printable_area;
|
||||
bed_exclude_area = other.bed_exclude_area;
|
||||
wrapping_exclude_area = other.wrapping_exclude_area;
|
||||
toolpath_outside = other.toolpath_outside;
|
||||
label_object_enabled = other.label_object_enabled;
|
||||
long_retraction_when_cut = other.long_retraction_when_cut;
|
||||
timelapse_warning_code = other.timelapse_warning_code;
|
||||
printable_height = other.printable_height;
|
||||
settings_ids = other.settings_ids;
|
||||
filaments_count = other.filaments_count;
|
||||
extruder_colors = other.extruder_colors;
|
||||
filament_diameters = other.filament_diameters;
|
||||
filament_densities = other.filament_densities;
|
||||
filament_costs = other.filament_costs;
|
||||
print_statistics = other.print_statistics;
|
||||
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
|
||||
spiral_vase_mode = other.spiral_vase_mode;
|
||||
warnings = other.warnings;
|
||||
bed_type = other.bed_type;
|
||||
gcode_check_result = other.gcode_check_result;
|
||||
limit_filament_maps = other.limit_filament_maps;
|
||||
filament_printable_reuslt = other.filament_printable_reuslt;
|
||||
filename = std::forward<Other>(other).filename;
|
||||
id = std::forward<Other>(other).id;
|
||||
moves = std::forward<Other>(other).moves;
|
||||
lines_ends = std::forward<Other>(other).lines_ends;
|
||||
printable_area = std::forward<Other>(other).printable_area;
|
||||
bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
|
||||
wrapping_exclude_area = std::forward<Other>(other).wrapping_exclude_area;
|
||||
toolpath_outside = std::forward<Other>(other).toolpath_outside;
|
||||
label_object_enabled = std::forward<Other>(other).label_object_enabled;
|
||||
long_retraction_when_cut = std::forward<Other>(other).long_retraction_when_cut;
|
||||
timelapse_warning_code = std::forward<Other>(other).timelapse_warning_code;
|
||||
printable_height = std::forward<Other>(other).printable_height;
|
||||
settings_ids = std::forward<Other>(other).settings_ids;
|
||||
filaments_count = std::forward<Other>(other).filaments_count;
|
||||
extruder_colors = std::forward<Other>(other).extruder_colors;
|
||||
filament_diameters = std::forward<Other>(other).filament_diameters;
|
||||
filament_densities = std::forward<Other>(other).filament_densities;
|
||||
filament_costs = std::forward<Other>(other).filament_costs;
|
||||
print_statistics = std::forward<Other>(other).print_statistics;
|
||||
custom_gcode_per_print_z = std::forward<Other>(other).custom_gcode_per_print_z;
|
||||
spiral_vase_mode = std::forward<Other>(other).spiral_vase_mode;
|
||||
warnings = std::forward<Other>(other).warnings;
|
||||
bed_type = std::forward<Other>(other).bed_type;
|
||||
gcode_check_result = std::forward<Other>(other).gcode_check_result;
|
||||
limit_filament_maps = std::forward<Other>(other).limit_filament_maps;
|
||||
filament_printable_reuslt = std::forward<Other>(other).filament_printable_reuslt;
|
||||
// Orca: copy the shared grouping result so a copied result keeps it (shared_ptr =>
|
||||
// memory-safe), rather than leaving a stale pointer on the target. No g-code effect either way.
|
||||
nozzle_group_result = other.nozzle_group_result;
|
||||
nozzle_group_result = std::forward<Other>(other).nozzle_group_result;
|
||||
// Keep the per-extruder hotend types on a copied result (injector input).
|
||||
extruder_types = other.extruder_types;
|
||||
printer_extruder_variant = other.printer_extruder_variant;
|
||||
printer_extruder_id = other.printer_extruder_id;
|
||||
layer_filaments = other.layer_filaments;
|
||||
filament_change_sequence = other.filament_change_sequence;
|
||||
used_mixed_filaments = other.used_mixed_filaments;
|
||||
nozzle_change_sequence = other.nozzle_change_sequence;
|
||||
optimal_assignment = other.optimal_assignment;
|
||||
filament_change_count_map = other.filament_change_count_map;
|
||||
extruder_types = std::forward<Other>(other).extruder_types;
|
||||
printer_extruder_variant = std::forward<Other>(other).printer_extruder_variant;
|
||||
printer_extruder_id = std::forward<Other>(other).printer_extruder_id;
|
||||
layer_filaments = std::forward<Other>(other).layer_filaments;
|
||||
filament_change_sequence = std::forward<Other>(other).filament_change_sequence;
|
||||
used_mixed_filaments = std::forward<Other>(other).used_mixed_filaments;
|
||||
nozzle_change_sequence = std::forward<Other>(other).nozzle_change_sequence;
|
||||
optimal_assignment = std::forward<Other>(other).optimal_assignment;
|
||||
filament_change_count_map = std::forward<Other>(other).filament_change_count_map;
|
||||
// Keep the SKIPPABLE per-type time on a copied result.
|
||||
skippable_part_time = other.skippable_part_time;
|
||||
initial_layer_time = other.initial_layer_time;
|
||||
skippable_part_time = std::forward<Other>(other).skippable_part_time;
|
||||
initial_layer_time = std::forward<Other>(other).initial_layer_time;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
time = std::forward<Other>(other).time;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
void lock() const { result_mutex.lock(); }
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
@@ -1216,6 +1219,9 @@ class Print;
|
||||
EProducer m_producer;
|
||||
|
||||
TimeProcessor m_time_processor;
|
||||
// calculate_time()'s map from each block's move id to its index after the actual speed moves are inserted,
|
||||
// a member to reuse its capacity.
|
||||
std::vector<std::pair<unsigned int, unsigned int>> m_actual_speed_id_map;
|
||||
UsedFilaments m_used_filaments;
|
||||
|
||||
Print* m_print{ nullptr };
|
||||
|
||||
@@ -465,32 +465,31 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
|
||||
if (EXTRUDER_LIMIT(m_max_acceleration) > 0 && acceleration > EXTRUDER_LIMIT(m_max_acceleration))
|
||||
acceleration = EXTRUDER_LIMIT(m_max_acceleration);
|
||||
|
||||
bool is_empty = true;
|
||||
std::ostringstream gcode;
|
||||
gcode << "SET_VELOCITY_LIMIT";
|
||||
if (acceleration != 0 && acceleration != m_last_acceleration) {
|
||||
gcode << " ACCEL=" << acceleration;
|
||||
if (this->config.accel_to_decel_enable) {
|
||||
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
|
||||
}
|
||||
m_last_acceleration = acceleration;
|
||||
is_empty = false;
|
||||
}
|
||||
// Clamp the jerk to the allowed maximum.
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_x))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_x);
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_y))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_y);
|
||||
|
||||
if (jerk > 0.01 && !is_approx(jerk, m_last_jerk)) {
|
||||
const bool set_acceleration = acceleration != 0 && acceleration != m_last_acceleration;
|
||||
const bool set_jerk = jerk > 0.01 && !is_approx(jerk, m_last_jerk);
|
||||
if (!set_acceleration && !set_jerk)
|
||||
return std::string();
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << "SET_VELOCITY_LIMIT";
|
||||
if (set_acceleration) {
|
||||
gcode << " ACCEL=" << acceleration;
|
||||
if (this->config.accel_to_decel_enable) {
|
||||
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
|
||||
}
|
||||
m_last_acceleration = acceleration;
|
||||
}
|
||||
if (set_jerk) {
|
||||
gcode << " SQUARE_CORNER_VELOCITY=" << jerk;
|
||||
m_last_jerk = jerk;
|
||||
is_empty = false;
|
||||
}
|
||||
|
||||
if(is_empty)
|
||||
return std::string();
|
||||
|
||||
if (GCodeWriter::full_gcode_comment)
|
||||
gcode << " ; adjust VELOCITY_LIMIT(accel/jerk)";
|
||||
gcode << "\n";
|
||||
|
||||
@@ -84,6 +84,19 @@ double string_to_double_decimal_point(const std::string_view str, size_t* pos /*
|
||||
return out;
|
||||
}
|
||||
|
||||
double atof_decimal_point(std::string_view str)
|
||||
{
|
||||
size_t i = 0;
|
||||
while (i < str.size() && (str[i] == ' ' || (str[i] >= '\t' && str[i] <= '\r')))
|
||||
++i;
|
||||
if (i < str.size() && str[i] == '+') {
|
||||
++i;
|
||||
if (i < str.size() && str[i] == '-')
|
||||
return 0.;
|
||||
}
|
||||
return string_to_double_decimal_point(str.substr(i));
|
||||
}
|
||||
|
||||
std::string float_to_string_decimal_point(double value, int precision/* = -1*/)
|
||||
{
|
||||
// Our Windows build server fully supports C++17 std::to_chars. Let's use it.
|
||||
|
||||
@@ -48,6 +48,9 @@ bool is_decimal_separator_point();
|
||||
std::string float_to_string_decimal_point(double value, int precision = -1);
|
||||
//std::string float_to_string_decimal_point(float value, int precision = -1);
|
||||
double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr);
|
||||
// Parses like atof in the C locale, skipping leading whitespace and a '+',
|
||||
// without the C runtime's per-call locale lookup.
|
||||
double atof_decimal_point(std::string_view str);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@@ -302,6 +304,14 @@ extern bool is_absolute_path_within_root(const boost::filesystem::path &path, co
|
||||
// Anything unknown is not safe.
|
||||
extern bool is_safe_to_open_file_name(const std::string &file_name);
|
||||
|
||||
// Case-insensitive compare against a fixed ASCII keyword, without boost::iequals, whose
|
||||
// std::locale() takes a lock the whole process shares in the MSVC runtime.
|
||||
inline bool ascii_iequals(std::string_view a, std::string_view b)
|
||||
{
|
||||
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
|
||||
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [&lower](char x, char y) { return lower(x) == lower(y); });
|
||||
}
|
||||
|
||||
// Orca: custom protocal support utils
|
||||
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
|
||||
inline bool is_prusaslicer_open(const std::string& url) { return boost::starts_with(url, "prusaslicer://open"); }
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "test_utils.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -79,3 +84,90 @@ TEST_CASE("Reserved keyword detection reports every offending line", "[GCodeProc
|
||||
CHECK(tags.empty());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Closed outer-wall squares, each after a fast travel and before an inner-wall move, so the processor
|
||||
// records seams and inserts actual speed moves. virtual_moves adds a VG1 move after each square.
|
||||
void process_squares(int squares, GCodeProcessorResult &result, bool virtual_moves = false)
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
gcode << "M83\nG90\n";
|
||||
for (int i = 0; i < squares; ++i) {
|
||||
gcode << "G1 X10 Y10 Z" << 0.2 * (i + 1) << " F12000\n"
|
||||
<< "; FEATURE: Outer wall\n"
|
||||
<< "G1 X50 Y10 E2 F3000\nG1 X50 Y50 E2\nG1 X10 Y50 E2\nG1 X10 Y10 E2\n"
|
||||
<< "; FEATURE: Inner wall\n"
|
||||
<< "G1 X12 Y12 E0.1\nG1 X30 Y12 E1\n";
|
||||
if (virtual_moves)
|
||||
gcode << "VG1 X20 Y30 F12000\n";
|
||||
}
|
||||
FullPrintConfig config;
|
||||
config.gcode_flavor.value = gcfMarlinFirmware;
|
||||
// s_IsBBLPrinter selects the "; FEATURE: " role tags this G-code uses.
|
||||
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
|
||||
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
|
||||
GCodeProcessor::s_IsBBLPrinter = true;
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
std::ofstream(temp.string()) << gcode.str();
|
||||
GCodeProcessor processor;
|
||||
processor.apply_config(config);
|
||||
processor.process_file(temp.string());
|
||||
result = std::move(processor.extract_result());
|
||||
}
|
||||
|
||||
bool is_block_move(const GCodeProcessorResult::MoveVertex &move)
|
||||
{
|
||||
return !move.internal_only && (move.type == EMoveType::Extrude || move.type == EMoveType::Travel);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Actual speed moves are inserted on their block's segment just before its move", "[GCodeProcessor]")
|
||||
{
|
||||
// 60 squares take several planner passes, which remap the blocks kept between passes.
|
||||
const int squares = GENERATE(10, 60);
|
||||
const bool virtual_moves = GENERATE(false, true);
|
||||
GCodeProcessorResult result;
|
||||
process_squares(squares, result, virtual_moves);
|
||||
const auto &moves = result.moves;
|
||||
constexpr size_t normal = size_t(PrintEstimatedStatistics::ETimeMode::Normal);
|
||||
|
||||
size_t inserted = 0;
|
||||
for (size_t i = 1; i < moves.size(); ++i) {
|
||||
if (!moves[i].internal_only)
|
||||
continue;
|
||||
++inserted;
|
||||
// Inserted moves have zero time, but a VG1 block's time is written to whatever move its move_id names.
|
||||
if (!virtual_moves)
|
||||
CHECK(moves[i].time[normal] == 0.f);
|
||||
size_t block = i + 1;
|
||||
while (block < moves.size() && moves[block].internal_only)
|
||||
++block;
|
||||
size_t previous = i - 1;
|
||||
while (previous > 0 && moves[previous].internal_only)
|
||||
--previous;
|
||||
REQUIRE(block < moves.size());
|
||||
CHECK(moves[block].gcode_id == moves[i].gcode_id);
|
||||
const Vec3f segment = moves[block].position - moves[previous].position;
|
||||
const Vec3f offset = moves[i].position - moves[previous].position;
|
||||
CHECK(segment.cross(offset).norm() / segment.norm() < 1e-3f);
|
||||
}
|
||||
REQUIRE(inserted > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("A seam takes the actual speed of the move it follows", "[GCodeProcessor]")
|
||||
{
|
||||
GCodeProcessorResult result;
|
||||
// 10 squares fit in one planner pass, so the seam's move and the block after it are timed together.
|
||||
process_squares(10, result);
|
||||
const auto &moves = result.moves;
|
||||
|
||||
size_t seams = 0;
|
||||
for (size_t i = 1; i < moves.size(); ++i)
|
||||
if (moves[i].type == EMoveType::Seam && is_block_move(moves[i - 1])) {
|
||||
++seams;
|
||||
CHECK_THAT(moves[i].actual_feedrate, Catch::Matchers::WithinAbs(moves[i - 1].actual_feedrate, 1e-4));
|
||||
}
|
||||
REQUIRE(seams > 0);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -842,6 +843,47 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant", "[M
|
||||
}
|
||||
}
|
||||
|
||||
// A two-extruder printer, Standard nozzle on extruder 1 and High Flow on extruder 2, whose per-variant arrays
|
||||
// hold filament 1 Standard, filament 1 High Flow, filament 2 Standard and filament 2 High Flow.
|
||||
static DynamicPrintConfig two_extruder_pressure_advance_config(const std::string &filament_map, const std::string &adaptive_pressure_advance,
|
||||
int wall_filament, int infill_filament)
|
||||
{
|
||||
DynamicPrintConfig config = multifilament_config(2, {
|
||||
{ "gcode_flavor", "klipper" },
|
||||
{ "single_extruder_multi_material", 0 },
|
||||
{ "nozzle_diameter", "0.4,0.4" },
|
||||
{ "extruder_printable_height", "0,0" },
|
||||
{ "printer_extruder_id", "1,2" },
|
||||
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_map", filament_map },
|
||||
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_self_index", "1,1,2,2" },
|
||||
{ "enable_pressure_advance", "1,1,1,1" },
|
||||
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
|
||||
{ "adaptive_pressure_advance", adaptive_pressure_advance },
|
||||
{ "sparse_infill_filament_id", infill_filament },
|
||||
{ "internal_solid_filament_id", infill_filament },
|
||||
{ "top_surface_filament_id", infill_filament },
|
||||
{ "bottom_surface_filament_id", infill_filament },
|
||||
{ "outer_wall_filament_id", wall_filament },
|
||||
{ "inner_wall_filament_id", wall_filament },
|
||||
{ "enable_prime_tower", 0 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "no_brim" },
|
||||
// custom G-code indexes the per-filament arrays by filament
|
||||
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||
});
|
||||
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
|
||||
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
|
||||
// print each filament on the extruder filament_map gives it
|
||||
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
|
||||
return config;
|
||||
}
|
||||
|
||||
// On a printer with two extruders, a filament takes the pressure advance of the variant of the extruder
|
||||
// it is mapped to, whichever filament and extruder that is.
|
||||
TEST_CASE("Each filament sets the pressure advance of its extruder variant on a two-extruder printer", "[MultiFilament]")
|
||||
@@ -857,42 +899,8 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant on a
|
||||
// the other filament goes on the other extruder
|
||||
const std::string filament_map = filament == 1 ? std::to_string(extruder) + "," + std::to_string(3 - extruder) :
|
||||
std::to_string(3 - extruder) + "," + std::to_string(extruder);
|
||||
DynamicPrintConfig config = multifilament_config(2, {
|
||||
{ "gcode_flavor", "klipper" },
|
||||
{ "single_extruder_multi_material", 0 },
|
||||
{ "nozzle_diameter", "0.4,0.4" },
|
||||
{ "extruder_printable_height", "0,0" },
|
||||
// extruder 1 has a Standard nozzle, extruder 2 a High Flow one
|
||||
{ "printer_extruder_id", "1,2" },
|
||||
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_map", filament_map },
|
||||
// both filaments define Standard and High Flow
|
||||
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_self_index", "1,1,2,2" },
|
||||
{ "enable_pressure_advance", "1,1,1,1" },
|
||||
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
|
||||
{ "adaptive_pressure_advance", adaptive ? "1,1,1,1" : "0,0,0,0" },
|
||||
{ "sparse_infill_filament_id", filament },
|
||||
{ "internal_solid_filament_id", filament },
|
||||
{ "top_surface_filament_id", filament },
|
||||
{ "bottom_surface_filament_id", filament },
|
||||
{ "outer_wall_filament_id", filament },
|
||||
{ "inner_wall_filament_id", filament },
|
||||
{ "enable_prime_tower", 0 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "no_brim" },
|
||||
// custom G-code indexes the per-filament arrays by filament
|
||||
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||
});
|
||||
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
|
||||
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
|
||||
// keep the mapping above rather than grouping the filaments automatically
|
||||
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
|
||||
const std::string gcode = slice({ cube(20) }, config);
|
||||
const std::string gcode = slice({ cube(20) },
|
||||
two_extruder_pressure_advance_config(filament_map, adaptive ? "1,1,1,1" : "0,0,0,0", filament, filament));
|
||||
|
||||
std::set<std::string> expected{ pressure_advance };
|
||||
if (adaptive)
|
||||
@@ -902,6 +910,82 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant on a
|
||||
}
|
||||
}
|
||||
|
||||
// Filament 1 prints the walls on extruder 1 (variant index 0), filament 2 the infill on extruder 2 (variant index 3).
|
||||
TEST_CASE("Adaptive pressure advance on one extruder leaves the other extruder's pressure advance alone", "[MultiFilament]")
|
||||
{
|
||||
auto [adaptive, expected] = GENERATE(table<std::string, std::set<std::string>>({
|
||||
{ "1,0,0,0", { "0.021", "0.049", "0.012" } },
|
||||
{ "0,0,0,1", { "0.021", "0.049", "0.078" } },
|
||||
}));
|
||||
DYNAMIC_SECTION("adaptive " << adaptive) {
|
||||
const std::string gcode = slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", adaptive, 1, 2));
|
||||
CHECK(pressure_advance_values(gcode) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// The pressure advance values a Klipper G-code sets while `tool` is active, in order, without repeating the value already set.
|
||||
static std::vector<std::string> pressure_advance_sequence(const std::string &gcode, int tool)
|
||||
{
|
||||
const std::string token = "SET_PRESSURE_ADVANCE ADVANCE=";
|
||||
std::vector<std::string> values;
|
||||
int current_tool = 0;
|
||||
std::istringstream stream(gcode);
|
||||
for (std::string line; std::getline(stream, line);) {
|
||||
if (line.size() > 1 && line[0] == 'T' && std::isdigit((unsigned char) line[1]))
|
||||
current_tool = std::stoi(line.substr(1));
|
||||
else if (current_tool == tool && line.rfind(token, 0) == 0) {
|
||||
std::string value = line.substr(token.size(), line.find(';') - token.size());
|
||||
if (values.empty() || values.back() != value)
|
||||
values.push_back(std::move(value));
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
TEST_CASE("Adaptive pressure advance predicts the same values after layers only a non-adaptive extruder prints", "[MultiFilament]")
|
||||
{
|
||||
auto slice_with = [](const std::string &adaptive) {
|
||||
DynamicPrintConfig config = two_extruder_pressure_advance_config("1,2", adaptive, 1, 1);
|
||||
config.set_deserialize_strict({
|
||||
{ "print_sequence", "by object" },
|
||||
// extruder 2 moves at one speed on its first layer, so no G1 F follows its first PA_CHANGE tag
|
||||
{ "filament_max_volumetric_speed", "100" },
|
||||
{ "slow_down_for_layer_cooling", "0" },
|
||||
{ "travel_speed", "120,40" },
|
||||
{ "retraction_speed", "30,40" },
|
||||
{ "deretraction_speed", "30,40" },
|
||||
{ "initial_layer_speed", "30,40" },
|
||||
{ "initial_layer_infill_speed", "60,40" },
|
||||
});
|
||||
auto &models = config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values;
|
||||
// with adaptive pressure advance on, filament 1 gets PA_CHANGE tags on every layer but keeps its pressure advance
|
||||
models[0] = constant_pressure_advance_model("0.021");
|
||||
// a prediction that rises with flow, so it depends on the print speed
|
||||
models[3] = "0.01,1,1000\n0.09,40,1000\n0.01,1,100000\n0.09,40,100000";
|
||||
return slice_with_object_overrides({ cube(20), cube(20) }, config,
|
||||
{ {}, { { "outer_wall_filament_id", 2 }, { "inner_wall_filament_id", 2 }, { "sparse_infill_filament_id", 2 },
|
||||
{ "internal_solid_filament_id", 2 }, { "top_surface_filament_id", 2 }, { "bottom_surface_filament_id", 2 } } });
|
||||
};
|
||||
const std::vector<std::string> expected = pressure_advance_sequence(slice_with("1,0,0,1"), 1);
|
||||
REQUIRE(expected.size() > 2);
|
||||
CHECK(pressure_advance_sequence(slice_with("0,0,0,1"), 1) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Adaptive pressure advance on an unused extruder variant leaves the G-code unchanged", "[MultiFilament]")
|
||||
{
|
||||
const std::string adaptive = GENERATE("0,1,0,0", "0,0,1,0");
|
||||
DYNAMIC_SECTION("adaptive " << adaptive) {
|
||||
// the time and object ids differ between any two slices, and the config block lists the setting itself
|
||||
auto masked = [](const std::string &gcode) {
|
||||
return std::regex_replace(gcode.substr(0, gcode.find("; CONFIG_BLOCK_START")), std::regex("; generated by .*| id:\\d+"), "");
|
||||
};
|
||||
const std::string reference = masked(slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", "0,0,0,0", 1, 2)));
|
||||
const std::string gcode = masked(slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", adaptive, 1, 2)));
|
||||
REQUIRE(reference.find("SET_PRESSURE_ADVANCE") != std::string::npos);
|
||||
CHECK(gcode == reference);
|
||||
}
|
||||
}
|
||||
|
||||
// The speeds, in percent, a G-code turns a fan on at: the part cooling fan for `M106 S`, the auxiliary
|
||||
// fan for `M106 P2 S`.
|
||||
static std::set<int> fan_speeds(const std::string &gcode, const std::string &command)
|
||||
|
||||
@@ -69,3 +69,22 @@ TEST_CASE("a setter nested in another sets C again when the locale changed betwe
|
||||
CHECK_FALSE(is_decimal_separator_point());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("atof_decimal_point parses what atof parses in the C locale", "[LocalesUtils]")
|
||||
{
|
||||
const auto [text, value] = GENERATE(table<const char*, double>({
|
||||
{ "5", 5. },
|
||||
{ " 12.5", 12.5 },
|
||||
{ "\t+3", 3. },
|
||||
{ "\r\n7", 7. },
|
||||
{ "-1.25", -1.25 },
|
||||
{ "1e2", 100. },
|
||||
{ ".5", 0.5 },
|
||||
{ "12.5;comment", 12.5 },
|
||||
{ "+-5", 0. },
|
||||
{ "", 0. },
|
||||
{ "abc", 0. },
|
||||
}));
|
||||
INFO(text);
|
||||
CHECK_THAT(atof_decimal_point(text), Catch::Matchers::WithinAbs(value, 1e-12));
|
||||
}
|
||||
|
||||
@@ -509,3 +509,15 @@ TEST_CASE("is_safe_to_open_file_name rejects programs and anything it does not k
|
||||
INFO(unsafe);
|
||||
CHECK_FALSE(is_safe_to_open_file_name(unsafe));
|
||||
}
|
||||
|
||||
TEST_CASE("ascii_iequals compares ASCII letters regardless of case", "[Utils]") {
|
||||
CHECK(ascii_iequals("set_velocity_limit", "SET_VELOCITY_LIMIT"));
|
||||
CHECK(ascii_iequals("G28", "g28"));
|
||||
CHECK(ascii_iequals("", ""));
|
||||
CHECK_FALSE(ascii_iequals("G28", "G29"));
|
||||
CHECK_FALSE(ascii_iequals("G2", "G28"));
|
||||
CHECK_FALSE(ascii_iequals("G28", "G2"));
|
||||
// Non-letters 0x20 apart are not equal.
|
||||
CHECK_FALSE(ascii_iequals("[", "{"));
|
||||
CHECK_FALSE(ascii_iequals("@", "`"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user