mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 06:41:10 +00:00
perf: speed up G-code export by up to 8% via cheaper G-code text building (#16108)
This commit is contained in:
+44
-38
@@ -5204,7 +5204,8 @@ namespace Skirt {
|
||||
// Orca: Klipper can't parse object names with spaces and other spetical characters
|
||||
std::string sanitize_instance_name(const std::string& name) {
|
||||
// Replace sequences of non-word characters with an underscore
|
||||
std::string result = std::regex_replace(name, std::regex("[ !@#$%^&*()=+\\[\\]{};:\",']+"), "_");
|
||||
static const std::regex non_word_characters("[ !@#$%^&*()=+\\[\\]{};:\",']+");
|
||||
std::string result = std::regex_replace(name, non_word_characters, "_");
|
||||
// Remove leading and trailing underscores
|
||||
if (!result.empty() && result.front() == '_') {
|
||||
result.erase(result.begin());
|
||||
@@ -5218,12 +5219,16 @@ std::string sanitize_instance_name(const std::string& name) {
|
||||
|
||||
inline std::string get_instance_name(const PrintObject *object, size_t inst_id) {
|
||||
auto obj_name = sanitize_instance_name(object->model_object()->name);
|
||||
auto name = (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_id() % inst_id).str();
|
||||
auto name = obj_name + "_id_" + std::to_string(object->get_id()) + "_copy_" + std::to_string(inst_id);
|
||||
return sanitize_instance_name(name);
|
||||
}
|
||||
|
||||
inline std::string get_instance_name(const PrintObject *object, const PrintInstance &inst) {
|
||||
return get_instance_name(object, inst.id);
|
||||
const std::string& GCode::instance_name(const PrintInstance &instance)
|
||||
{
|
||||
auto [it, inserted] = m_instance_names.try_emplace(&instance);
|
||||
if (inserted)
|
||||
it->second = get_instance_name(instance.print_object, instance.id);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string GCode::generate_skirt(const Print &print,
|
||||
@@ -6642,7 +6647,7 @@ LayerResult GCode::process_layer(
|
||||
const auto gflavor = print.config().gcode_flavor.value;
|
||||
if (gflavor == gcfKlipper) {
|
||||
m_writer.set_object_start_str(std::string("EXCLUDE_OBJECT_START NAME=") +
|
||||
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
|
||||
instance_name(inst) + "\n");
|
||||
}
|
||||
else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
|
||||
std::string str = std::string("M486 S") + std::to_string(inst.unique_id) + "\n";
|
||||
@@ -6791,7 +6796,7 @@ LayerResult GCode::process_layer(
|
||||
const auto gflavor = print.config().gcode_flavor.value;
|
||||
if (gflavor == gcfKlipper) {
|
||||
m_writer.set_object_end_str(std::string("EXCLUDE_OBJECT_END NAME=") +
|
||||
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
|
||||
instance_name(inst) + "\n");
|
||||
} else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
|
||||
m_writer.set_object_end_str(std::string("M486 S-1\n"));
|
||||
}
|
||||
@@ -6866,7 +6871,7 @@ LayerResult GCode::process_layer(
|
||||
const auto gflavor = print.config().gcode_flavor.value;
|
||||
if (gflavor == gcfKlipper) {
|
||||
m_writer.set_object_start_str(std::string("EXCLUDE_OBJECT_START NAME=") +
|
||||
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
|
||||
instance_name(inst) + "\n");
|
||||
} else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
|
||||
m_writer.set_object_start_str(std::string("M486 S") + std::to_string(inst.unique_id) + "\n");
|
||||
}
|
||||
@@ -7092,7 +7097,7 @@ LayerResult GCode::process_layer(
|
||||
const auto gflavor = print.config().gcode_flavor.value;
|
||||
if (gflavor == gcfKlipper) {
|
||||
m_writer.set_object_end_str(std::string("EXCLUDE_OBJECT_END NAME=") +
|
||||
get_instance_name(&instance_to_print.print_object, inst.id) + "\n");
|
||||
instance_name(inst) + "\n");
|
||||
} else if (gflavor == gcfMarlinLegacy || gflavor == gcfMarlinFirmware || gflavor == gcfRepRapFirmware) {
|
||||
m_writer.set_object_end_str(std::string("M486 S-1\n"));
|
||||
}
|
||||
@@ -8084,12 +8089,12 @@ static float overhang_fan_overlap_threshold(int overhang_fan_threshold)
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
|
||||
std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_description, double speed)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
if (is_bridge(path.role()))
|
||||
description += " (bridge)";
|
||||
const std::string bridge_description = is_bridge(path.role()) ? path_description + " (bridge)" : std::string();
|
||||
const std::string &description = bridge_description.empty() ? path_description : bridge_description;
|
||||
|
||||
const ExtrusionPathSloped* sloped = dynamic_cast<const ExtrusionPathSloped*>(&path);
|
||||
|
||||
@@ -8760,7 +8765,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// ORCA: End of adaptive PA code segment
|
||||
}
|
||||
|
||||
gcode += m_writer.set_speed(F, "", comment);
|
||||
m_writer.set_speed(gcode, F, "", comment);
|
||||
{
|
||||
if (m_enable_cooling_markers) {
|
||||
if (enable_overhang_bridge_fan) {
|
||||
@@ -8784,7 +8789,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
double saved_z = m_writer.get_position().z();
|
||||
|
||||
for (const Line3& line : path.polyline.lines()) {
|
||||
std::string tempDescription = description;
|
||||
std::string flow_description;
|
||||
const double line_length = line.length() * SCALING_FACTOR;
|
||||
if (line_length < EPSILON)
|
||||
continue;
|
||||
@@ -8795,7 +8800,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role());
|
||||
|
||||
if (m_config.gcode_comments && oldE > 0 && oldE != dE) {
|
||||
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
flow_description = description + Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
}
|
||||
}
|
||||
if (path.z_contoured) {
|
||||
@@ -8814,24 +8819,24 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
if (z < 0.1) {
|
||||
throw RuntimeError("GCode: very low z");
|
||||
}
|
||||
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "");
|
||||
m_writer.extrude_to_xyz(gcode, Vec3d(dest2d.x(), dest2d.y(), z), e,
|
||||
flow_description.empty() ? description : flow_description);
|
||||
|
||||
} else if (sloped == nullptr) {
|
||||
// Normal extrusion
|
||||
gcode += m_writer.extrude_to_xy(
|
||||
m_writer.extrude_to_xy(gcode,
|
||||
this->point_to_gcode(line.b.to_point()),
|
||||
dE,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
|
||||
flow_description.empty() ? description : flow_description, path.is_force_no_extrusion());
|
||||
} else {
|
||||
// Sloped extrusion
|
||||
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
|
||||
Vec2d dest2d = this->point_to_gcode(line.b.to_point());
|
||||
Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio));
|
||||
gcode += m_writer.extrude_to_xyz(
|
||||
m_writer.extrude_to_xyz(gcode,
|
||||
dest3d,
|
||||
dE * e_ratio,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
|
||||
flow_description.empty() ? description : flow_description, path.is_force_no_extrusion());
|
||||
}
|
||||
check_and_insert_timelapse(line.b.to_point()); // Inline farthest-point snapshot
|
||||
}
|
||||
@@ -8839,13 +8844,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// BBS: start to generate gcode from arc fitting data which includes line and arc
|
||||
const std::vector<PathFittingData>& fitting_result = path.polyline.fitting_result;
|
||||
for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) {
|
||||
std::string tempDescription = description;
|
||||
std::string flow_description;
|
||||
switch (fitting_result[fitting_index].path_type) {
|
||||
case EMovePathType::Linear_move: {
|
||||
size_t start_index = fitting_result[fitting_index].start_point_index;
|
||||
size_t end_index = fitting_result[fitting_index].end_point_index;
|
||||
for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) {
|
||||
tempDescription = description;
|
||||
flow_description.clear();
|
||||
const Line line = Line(path.polyline.points[point_index - 1].to_point(), path.polyline.points[point_index].to_point());
|
||||
const double line_length = line.length() * SCALING_FACTOR;
|
||||
if (line_length < EPSILON)
|
||||
@@ -8856,13 +8861,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role());
|
||||
|
||||
if (m_config.gcode_comments && oldE > 0 && oldE != dE) {
|
||||
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
flow_description = description + Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
}
|
||||
}
|
||||
gcode += m_writer.extrude_to_xy(
|
||||
m_writer.extrude_to_xy(gcode,
|
||||
this->point_to_gcode(line.b),
|
||||
dE,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
|
||||
flow_description.empty() ? description : flow_description, path.is_force_no_extrusion());
|
||||
check_and_insert_timelapse(line.b); // Inline farthest-point snapshot
|
||||
}
|
||||
break;
|
||||
@@ -8880,15 +8885,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
dE = m_small_area_infill_flow_compensator->modify_flow(arc_length, dE, path.role());
|
||||
|
||||
if (m_config.gcode_comments && oldE > 0 && oldE != dE) {
|
||||
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, arc_length);
|
||||
flow_description = description + Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, arc_length);
|
||||
}
|
||||
}
|
||||
gcode += m_writer.extrude_arc_to_xy(
|
||||
m_writer.extrude_arc_to_xy(gcode,
|
||||
this->point_to_gcode(arc.end_point),
|
||||
center_offset,
|
||||
dE,
|
||||
arc.direction == ArcDirection::Arc_Dir_CCW,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
|
||||
flow_description.empty() ? description : flow_description, path.is_force_no_extrusion());
|
||||
check_and_insert_timelapse(arc.end_point); // Inline farthest-point snapshot
|
||||
break;
|
||||
}
|
||||
@@ -8912,7 +8917,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
Polyline3 l(p);
|
||||
total_length = l.length() * SCALING_FACTOR;
|
||||
}
|
||||
gcode += m_writer.set_speed(last_set_speed, "", comment);
|
||||
m_writer.set_speed(gcode, last_set_speed, "", comment);
|
||||
Vec3d prev = this->point_to_gcode_quantized(new_points[0].p);
|
||||
bool pre_fan_enabled = false;
|
||||
bool cur_fan_enabled = false;
|
||||
@@ -8924,7 +8929,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
|
||||
double path_length = 0.;
|
||||
for (size_t i = 1; i < new_points.size(); i++) {
|
||||
std::string tempDescription = description;
|
||||
std::string flow_description;
|
||||
const ProcessedPoint &processed_point = new_points[i];
|
||||
const ProcessedPoint &pre_processed_point = new_points[i-1];
|
||||
Vec3d p = this->point_to_gcode_quantized(processed_point.p);
|
||||
@@ -8991,10 +8996,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// Ignore small speed variations - emit speed change if the delta between current and new is greater than 60mm/min / 1mm/sec
|
||||
// Reset speed to F if delta to F is less than 1mm/sec
|
||||
if ((std::abs(last_set_speed - new_speed) > 60)) {
|
||||
gcode += m_writer.set_speed(new_speed, "", comment);
|
||||
m_writer.set_speed(gcode, new_speed, "", comment);
|
||||
last_set_speed = new_speed;
|
||||
} else if ((std::abs(F - new_speed) <= 60)) {
|
||||
gcode += m_writer.set_speed(F, "", comment);
|
||||
m_writer.set_speed(gcode, F, "", comment);
|
||||
last_set_speed = F;
|
||||
}
|
||||
auto dE = e_per_mm * line_length;
|
||||
@@ -9003,7 +9008,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
dE = m_small_area_infill_flow_compensator->modify_flow(line_length, dE, path.role());
|
||||
|
||||
if (m_config.gcode_comments && oldE > 0 && oldE != dE) {
|
||||
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
flow_description = description + Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
|
||||
}
|
||||
}
|
||||
if (path.z_contoured) {
|
||||
@@ -9021,16 +9026,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
if (z < 0.1) {
|
||||
throw RuntimeError("GCode: very low z");
|
||||
}
|
||||
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
|
||||
GCodeWriter::full_gcode_comment ? tempDescription : "");
|
||||
m_writer.extrude_to_xyz(gcode, Vec3d(dest2d.x(), dest2d.y(), z), e,
|
||||
flow_description.empty() ? description : flow_description);
|
||||
} else if (sloped == nullptr) {
|
||||
// Normal extrusion
|
||||
gcode += m_writer.extrude_to_xy(p.head<2>(), dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
|
||||
m_writer.extrude_to_xy(gcode, p.head<2>(), dE, flow_description.empty() ? description : flow_description);
|
||||
} else {
|
||||
// Sloped extrusion
|
||||
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
|
||||
Vec3d dest3d(p(0), p(1), get_sloped_z(z_ratio));
|
||||
gcode += m_writer.extrude_to_xyz(dest3d, dE * e_ratio, GCodeWriter::full_gcode_comment ? tempDescription : "");
|
||||
m_writer.extrude_to_xyz(gcode, dest3d, dE * e_ratio, flow_description.empty() ? description : flow_description);
|
||||
}
|
||||
|
||||
// Inline farthest-point snapshot on the variable-speed emission path. Inert unless the
|
||||
@@ -10074,6 +10079,7 @@ std::string GCode::set_object_info(Print *print) {
|
||||
// PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed
|
||||
} else {
|
||||
size_t unique_id = 0;
|
||||
m_instance_names.clear();
|
||||
for (PrintObject* object : print->objects()) {
|
||||
object->set_id(object_id++);
|
||||
size_t inst_id = 0;
|
||||
@@ -10082,7 +10088,7 @@ std::string GCode::set_object_info(Print *print) {
|
||||
inst.id = inst_id++;
|
||||
auto bbox = inst.get_bounding_box();
|
||||
auto center = print->translate_to_print_space(Vec2d(bbox.center().x(), bbox.center().y()));
|
||||
auto inst_name = get_instance_name(object, inst);
|
||||
const std::string &inst_name = instance_name(inst);
|
||||
if (gflavor == gcfKlipper) {
|
||||
gcode << "EXCLUDE_OBJECT_DEFINE NAME=" << inst_name << " CENTER=" << center.x() << "," << center.y()
|
||||
<< " POLYGON=" << polygon_to_string(inst.get_convex_hull_2d(), print) << "\n";
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <cfloat>
|
||||
@@ -715,6 +716,9 @@ private:
|
||||
|
||||
bool m_enable_exclude_object;
|
||||
std::vector<size_t> m_label_objects_ids;
|
||||
// Object label names by instance, built on first use from the ids set_object_info() assigns.
|
||||
std::unordered_map<const PrintInstance*, std::string> m_instance_names;
|
||||
const std::string& instance_name(const PrintInstance &instance);
|
||||
std::string _encode_label_ids_to_base64(std::vector<size_t> ids);
|
||||
// ORCA: Add support for role based fan speed control
|
||||
std::array<bool, ExtrusionRole::erCount> m_is_role_based_fan_on;
|
||||
@@ -827,7 +831,7 @@ private:
|
||||
void update_layer_related_config(int layer_id);
|
||||
|
||||
double calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str);
|
||||
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
|
||||
std::string _extrude(const ExtrusionPath &path, const std::string &path_description = "", double speed = -1);
|
||||
bool _needSAFC(const ExtrusionPath &path);
|
||||
void print_machine_envelope(GCodeOutputStream& file, Print& print);
|
||||
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <Shiny/Shiny.h>
|
||||
#include <fast_float/fast_float.h>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
@@ -28,14 +29,24 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const GCodeConfig& GCodeReader::default_config()
|
||||
{
|
||||
static const GCodeConfig config;
|
||||
return config;
|
||||
}
|
||||
|
||||
void GCodeReader::apply_config(const GCodeConfig &config)
|
||||
{
|
||||
m_config = config;
|
||||
m_config = std::make_shared<const GCodeConfig>(config);
|
||||
m_use_relative_e_distances = config.use_relative_e_distances.value;
|
||||
}
|
||||
|
||||
void GCodeReader::apply_config(const DynamicPrintConfig &config)
|
||||
{
|
||||
m_config.apply(config, true);
|
||||
auto applied = std::make_shared<GCodeConfig>(m_config ? *m_config : default_config());
|
||||
applied->apply(config, true);
|
||||
m_use_relative_e_distances = applied->use_relative_e_distances.value;
|
||||
m_config = std::move(applied);
|
||||
}
|
||||
|
||||
const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command)
|
||||
@@ -95,7 +106,7 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G
|
||||
}
|
||||
}
|
||||
|
||||
if (gline.has(E) && m_config.use_relative_e_distances)
|
||||
if (gline.has(E) && m_use_relative_e_distances)
|
||||
m_position[E] = 0;
|
||||
|
||||
// Skip the rest of the line.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -111,7 +112,7 @@ public:
|
||||
void reset() { memset(m_position, 0, sizeof(m_position)); }
|
||||
void apply_config(const GCodeConfig &config);
|
||||
void apply_config(const DynamicPrintConfig &config);
|
||||
const GCodeConfig& config() { return m_config; };
|
||||
const GCodeConfig& config() { return m_config ? *m_config : default_config(); };
|
||||
|
||||
template<typename Callback>
|
||||
void parse_buffer(const std::string &buffer, Callback callback)
|
||||
@@ -172,7 +173,7 @@ public:
|
||||
|
||||
GCodeConfig get_config() const
|
||||
{
|
||||
return m_config;
|
||||
return m_config ? *m_config : default_config();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -199,8 +200,11 @@ private:
|
||||
return c;
|
||||
}
|
||||
static const char* axis_pos(const char *raw_str, char axis);
|
||||
static const GCodeConfig& default_config();
|
||||
|
||||
GCodeConfig m_config;
|
||||
// Set by apply_config() and shared by copies, so a reader without a config builds none.
|
||||
std::shared_ptr<const GCodeConfig> m_config;
|
||||
bool m_use_relative_e_distances{ default_config().use_relative_e_distances.value };
|
||||
float m_position[NUM_AXES];
|
||||
bool m_verbose;
|
||||
// To be set by the callback to stop parsing.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry/ArcWelder.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -380,26 +381,35 @@ std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned i
|
||||
|
||||
last_value = acceleration;
|
||||
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfRepetier))
|
||||
gcode << (separate_travel ? "M202 X" : "M201 X") << acceleration << " Y" << acceleration;
|
||||
else if (FLAVOR_IS(gcfRepRapFirmware) || FLAVOR_IS(gcfMarlinFirmware))
|
||||
gcode << (separate_travel ? "M204 T" : "M204 P") << acceleration;
|
||||
else if (FLAVOR_IS(gcfKlipper)) {
|
||||
gcode << "SET_VELOCITY_LIMIT ACCEL=" << acceleration;
|
||||
const std::string value = std::to_string(acceleration);
|
||||
std::string gcode;
|
||||
if (FLAVOR_IS(gcfRepetier)) {
|
||||
gcode += separate_travel ? "M202 X" : "M201 X";
|
||||
gcode += value;
|
||||
gcode += " Y";
|
||||
gcode += value;
|
||||
} else if (FLAVOR_IS(gcfRepRapFirmware) || FLAVOR_IS(gcfMarlinFirmware)) {
|
||||
gcode += separate_travel ? "M204 T" : "M204 P";
|
||||
gcode += value;
|
||||
} else if (FLAVOR_IS(gcfKlipper)) {
|
||||
gcode.reserve(96);
|
||||
gcode += "SET_VELOCITY_LIMIT ACCEL=";
|
||||
gcode += value;
|
||||
if (this->config.accel_to_decel_enable) {
|
||||
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
|
||||
gcode += " ACCEL_TO_DECEL=";
|
||||
gcode += float_to_string_decimal_point(acceleration * this->config.accel_to_decel_factor / 100);
|
||||
if (GCodeWriter::full_gcode_comment)
|
||||
gcode << " ; adjust ACCEL_TO_DECEL";
|
||||
gcode += " ; adjust ACCEL_TO_DECEL";
|
||||
}
|
||||
} else {
|
||||
gcode += "M204 S";
|
||||
gcode += value;
|
||||
}
|
||||
else
|
||||
gcode << "M204 S" << acceleration;
|
||||
|
||||
if (GCodeWriter::full_gcode_comment) gcode << " ; adjust acceleration";
|
||||
gcode << "\n";
|
||||
if (GCodeWriter::full_gcode_comment) gcode += " ; adjust acceleration";
|
||||
gcode += "\n";
|
||||
|
||||
return gcode.str();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_jerk_xy(double jerk)
|
||||
@@ -476,25 +486,29 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
|
||||
if (!set_acceleration && !set_jerk)
|
||||
return std::string();
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << "SET_VELOCITY_LIMIT";
|
||||
std::string gcode;
|
||||
gcode.reserve(96);
|
||||
gcode += "SET_VELOCITY_LIMIT";
|
||||
if (set_acceleration) {
|
||||
gcode << " ACCEL=" << acceleration;
|
||||
gcode += " ACCEL=";
|
||||
gcode += std::to_string(acceleration);
|
||||
if (this->config.accel_to_decel_enable) {
|
||||
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
|
||||
gcode += " ACCEL_TO_DECEL=";
|
||||
gcode += float_to_string_decimal_point(acceleration * this->config.accel_to_decel_factor / 100);
|
||||
}
|
||||
m_last_acceleration = acceleration;
|
||||
}
|
||||
if (set_jerk) {
|
||||
gcode << " SQUARE_CORNER_VELOCITY=" << jerk;
|
||||
gcode += " SQUARE_CORNER_VELOCITY=";
|
||||
gcode += float_to_string_decimal_point(jerk);
|
||||
m_last_jerk = jerk;
|
||||
}
|
||||
|
||||
if (GCodeWriter::full_gcode_comment)
|
||||
gcode << " ; adjust VELOCITY_LIMIT(accel/jerk)";
|
||||
gcode << "\n";
|
||||
gcode += " ; adjust VELOCITY_LIMIT(accel/jerk)";
|
||||
gcode += "\n";
|
||||
|
||||
return gcode.str();
|
||||
return gcode;
|
||||
|
||||
}
|
||||
|
||||
@@ -753,6 +767,13 @@ double GCodeWriter::get_extruder_retracted_length(const int filament_id)
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker)
|
||||
{
|
||||
std::string gcode;
|
||||
this->set_speed(gcode, F, comment, cooling_marker);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCodeWriter::set_speed(std::string &out, double F, const std::string &comment, const std::string &cooling_marker)
|
||||
{
|
||||
assert(F > 0.);
|
||||
assert(F < 100000.);
|
||||
@@ -763,7 +784,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
w.emit_string(cooling_marker);
|
||||
return w.string();
|
||||
w.append_to(out);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
|
||||
@@ -1115,6 +1136,13 @@ bool GCodeWriter::will_move_z(double z) const
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||
{
|
||||
std::string gcode;
|
||||
this->extrude_to_xy(gcode, point, dE, comment, force_no_extrusion);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCodeWriter::extrude_to_xy(std::string &out, const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||
{
|
||||
m_pos(0) = point(0);
|
||||
m_pos(1) = point(1);
|
||||
@@ -1133,13 +1161,20 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
||||
w.emit_e(filament()->E());
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
return w.string();
|
||||
w.append_to(out);
|
||||
}
|
||||
|
||||
//BBS: generate G2 or G3 extrude which moves by arc
|
||||
//point is end point which means X and Y axis
|
||||
//center_offset is I and J axis
|
||||
std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string& comment, bool force_no_extrusion)
|
||||
{
|
||||
std::string gcode;
|
||||
this->extrude_arc_to_xy(gcode, point, center_offset, dE, is_ccw, comment, force_no_extrusion);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCodeWriter::extrude_arc_to_xy(std::string &out, const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string& comment, bool force_no_extrusion)
|
||||
{
|
||||
m_pos(0) = point(0);
|
||||
m_pos(1) = point(1);
|
||||
@@ -1155,10 +1190,17 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent
|
||||
w.emit_e(filament()->E());
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
return w.string();
|
||||
w.append_to(out);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||
{
|
||||
std::string gcode;
|
||||
this->extrude_to_xyz(gcode, point, dE, comment, force_no_extrusion);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCodeWriter::extrude_to_xyz(std::string &out, const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||
{
|
||||
// Check if Z actually changes (at export precision) before emitting it.
|
||||
// ZAA sloped extrusions call this for every segment, but many consecutive
|
||||
@@ -1182,7 +1224,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
|
||||
w.emit_e(filament()->E());
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
return w.string();
|
||||
w.append_to(out);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::retract(bool before_wipe, double retract_length)
|
||||
|
||||
@@ -92,6 +92,11 @@ public:
|
||||
//BBS: generate G2 or G3 extrude which moves by arc
|
||||
std::string extrude_arc_to_xy(const Vec2d &point, const Vec2d ¢er_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
// Each appends its line to `out`.
|
||||
void set_speed(std::string &out, double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
|
||||
void extrude_to_xy(std::string &out, const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
void extrude_arc_to_xy(std::string &out, const Vec2d &point, const Vec2d ¢er_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
void extrude_to_xyz(std::string &out, const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
std::string retract(bool before_wipe = false, double retract_length = 0);
|
||||
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
|
||||
// extra_retract adds a small over-extrusion to the deretract move (PETG pre-extrusion).
|
||||
@@ -299,8 +304,14 @@ public:
|
||||
}
|
||||
|
||||
std::string string() {
|
||||
std::string out;
|
||||
this->append_to(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void append_to(std::string &out) {
|
||||
*ptr_err.ptr ++ = '\n';
|
||||
return std::string(this->buf, ptr_err.ptr - buf);
|
||||
out.append(this->buf, ptr_err.ptr - buf);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -6,15 +6,22 @@
|
||||
#include <string>
|
||||
#include <ios>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <charconv>
|
||||
#endif
|
||||
#include <charconv>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
#include <fast_float/fast_float.h>
|
||||
|
||||
// Defined where the floating point std::to_chars can be called, which with Apple's libc++ runtime is from macOS 13.3.
|
||||
#if defined(_LIBCPP_VERSION)
|
||||
#if defined(_LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT) && _LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT
|
||||
#define SLIC3R_FLOAT_TO_CHARS
|
||||
#endif
|
||||
#elif defined(_WIN32) || defined(__cpp_lib_to_chars)
|
||||
#define SLIC3R_FLOAT_TO_CHARS
|
||||
#endif
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -99,9 +106,9 @@ double atof_decimal_point(std::string_view str)
|
||||
|
||||
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.
|
||||
// Other platforms are behind, fall back to slow stringstreams for now.
|
||||
#ifdef _WIN32
|
||||
// Every branch prints the same digits in the classic locale as the stream at the end, which takes over when a branch
|
||||
// is compiled out or the value is too long for the buffer.
|
||||
#if defined(SLIC3R_FLOAT_TO_CHARS)
|
||||
constexpr size_t SIZE = 20;
|
||||
char out[SIZE] = "";
|
||||
std::to_chars_result res;
|
||||
@@ -109,16 +116,26 @@ std::string float_to_string_decimal_point(double value, int precision/* = -1*/)
|
||||
res = std::to_chars(out, out+SIZE, value, std::chars_format::fixed, precision);
|
||||
else
|
||||
res = std::to_chars(out, out+SIZE, value, std::chars_format::general, 6);
|
||||
if (res.ec == std::errc::value_too_large)
|
||||
throw std::invalid_argument("float_to_string_decimal_point conversion failed.");
|
||||
return std::string(out, res.ptr - out);
|
||||
#else
|
||||
if (res.ec == std::errc())
|
||||
return std::string(out, res.ptr - out);
|
||||
#elif defined(__APPLE__)
|
||||
// Formats in the C locale, as libc++'s stream does, switching only this thread's locale for the call.
|
||||
static const locale_t c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
|
||||
if (c_locale != (locale_t) 0) {
|
||||
constexpr size_t SIZE = 20;
|
||||
char out[SIZE];
|
||||
const locale_t previous = uselocale(c_locale);
|
||||
const int length = precision >= 0 ? snprintf(out, SIZE, "%.*f", precision, value) : snprintf(out, SIZE, "%.*g", 6, value);
|
||||
uselocale(previous);
|
||||
if (length >= 0 && size_t(length) < SIZE)
|
||||
return std::string(out, length);
|
||||
}
|
||||
#endif
|
||||
std::stringstream buf;
|
||||
if (precision >= 0)
|
||||
buf << std::fixed << std::setprecision(precision);
|
||||
buf << value;
|
||||
return buf.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_fill.cpp
|
||||
test_flow.cpp
|
||||
test_gcode_timing.cpp
|
||||
test_gcode.cpp
|
||||
test_gcodeprocessor.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_model.cpp
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Klipper object labels name each copy without the characters Klipper cannot parse", "[GCode]")
|
||||
{
|
||||
const auto [name, label] = GENERATE(table<std::string, std::string>({
|
||||
{"my part (2)", "my_part_2"},
|
||||
{"(cube)", "cube"},
|
||||
}));
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"gcode_flavor", "klipper"}, {"exclude_object", "1"}});
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print(std::vector<TriangleMesh>{Test::cube(20.)}, print, model, config, nullptr, false, 2);
|
||||
model.objects.front()->name = name;
|
||||
arrange_objects(model, BoundingBox{Point::new_scale(0., 0.), Point::new_scale(500., 500.)},
|
||||
ArrangeParams{scaled(min_object_distance(config))});
|
||||
print.apply(model, config);
|
||||
|
||||
const std::string gcode = Test::gcode(print);
|
||||
for (const char *copy : {"0", "1"}) {
|
||||
const std::string instance_label = label + "_id_0_copy_" + copy;
|
||||
INFO(instance_label);
|
||||
CHECK(gcode.find("EXCLUDE_OBJECT_DEFINE NAME=" + instance_label + " ") != std::string::npos);
|
||||
CHECK(gcode.find("EXCLUDE_OBJECT_START NAME=" + instance_label + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
@@ -454,6 +454,65 @@ TEST_CASE("EXTRUDER_LIMIT per-extruder clamping and max fallback", "[GCodeWriter
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Acceleration and velocity limit commands print their values in general notation", "[GCodeWriter]")
|
||||
{
|
||||
enum class Command { Print, Travel, KlipperLimits };
|
||||
struct Case
|
||||
{
|
||||
GCodeFlavor flavor;
|
||||
Command command;
|
||||
unsigned int acceleration;
|
||||
double jerk;
|
||||
bool comments;
|
||||
std::vector<std::string> present;
|
||||
std::vector<std::string> absent;
|
||||
};
|
||||
// accel_to_decel_factor is 50%, so ACCEL_TO_DECEL is half the acceleration.
|
||||
const Case c = GENERATE(values<Case>({
|
||||
{gcfKlipper, Command::KlipperLimits, 2000000, 25. / 3., false,
|
||||
{"SET_VELOCITY_LIMIT ACCEL=2000000 ", "ACCEL_TO_DECEL=1e+06 ", "SQUARE_CORNER_VELOCITY=8.33333\n"}, {}},
|
||||
{gcfKlipper, Command::KlipperLimits, 12345, 0., false, {"ACCEL=12345 ", "ACCEL_TO_DECEL=6172.5\n"}, {"SQUARE_CORNER_VELOCITY"}},
|
||||
{gcfKlipper, Command::KlipperLimits, 0, 0.25, true, {"SQUARE_CORNER_VELOCITY=0.25 ", "; adjust VELOCITY_LIMIT"}, {"ACCEL"}},
|
||||
{gcfKlipper, Command::Print, 3001, 0., true, {"ACCEL=3001 ", "ACCEL_TO_DECEL=1500.5 ", "; adjust ACCEL_TO_DECEL", "; adjust acceleration"}, {}},
|
||||
{gcfMarlinFirmware, Command::Print, 2500, 0., false, {"M204 P2500\n"}, {}},
|
||||
{gcfMarlinFirmware, Command::Travel, 7000, 0., false, {"M204 T7000\n"}, {}},
|
||||
{gcfRepRapFirmware, Command::Travel, 7000, 0., true, {"M204 T7000 ", "; adjust acceleration"}, {}},
|
||||
{gcfMarlinLegacy, Command::Print, 2500, 0., false, {"M204 S2500\n"}, {}},
|
||||
{gcfRepetier, Command::Print, 2500, 0., false, {"M201 X2500 Y2500\n"}, {}},
|
||||
{gcfRepetier, Command::Travel, 7000, 0., false, {"M202 X7000 Y7000\n"}, {}},
|
||||
}));
|
||||
|
||||
struct CommentGuard
|
||||
{
|
||||
bool saved = GCodeWriter::full_gcode_comment;
|
||||
~CommentGuard() { GCodeWriter::full_gcode_comment = saved; }
|
||||
} comment_guard;
|
||||
GCodeWriter::full_gcode_comment = c.comments;
|
||||
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_key_value("gcode_flavor", new ConfigOptionEnum<GCodeFlavor>(c.flavor));
|
||||
config.set_key_value("accel_to_decel_enable", new ConfigOptionBool(true));
|
||||
config.set_key_value("accel_to_decel_factor", new ConfigOptionPercent(50));
|
||||
for (const char *limit : {"machine_max_acceleration_extruding", "machine_max_acceleration_travel", "machine_max_acceleration_x",
|
||||
"machine_max_acceleration_y", "machine_max_jerk_x", "machine_max_jerk_y"}) {
|
||||
std::vector<double> &values = config.option<ConfigOptionFloats>(limit)->values;
|
||||
std::fill(values.begin(), values.end(), 0.);
|
||||
}
|
||||
PrintConfig print_config;
|
||||
print_config.apply(config, true);
|
||||
GCodeWriter writer;
|
||||
writer.apply_print_config(print_config);
|
||||
|
||||
const std::string line = c.command == Command::Print ? writer.set_print_acceleration(c.acceleration) :
|
||||
c.command == Command::Travel ? writer.set_travel_acceleration(c.acceleration) :
|
||||
writer.set_accel_and_jerk(c.acceleration, c.jerk);
|
||||
INFO(line);
|
||||
for (const std::string &token : c.present)
|
||||
CHECK_THAT(line, Catch::Matchers::ContainsSubstring(token));
|
||||
for (const std::string &token : c.absent)
|
||||
CHECK_THAT(line, !Catch::Matchers::ContainsSubstring(token));
|
||||
}
|
||||
|
||||
SCENARIO("Extruder reads the injected config column", "[GCodeWriter][H2C]") {
|
||||
GIVEN("A writer whose per-variant arrays hold three columns for two filaments") {
|
||||
GCodeWriter writer;
|
||||
|
||||
@@ -19,6 +19,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_config_variant_expansion.cpp
|
||||
test_locales_utils.cpp
|
||||
test_drc.cpp
|
||||
test_gcodereader.cpp
|
||||
test_toolordering_nozzle_group.cpp
|
||||
test_parallel_resolve.cpp
|
||||
test_preset_bundle_loading.cpp
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
// The extrusion of the second of two moves that both give E1.
|
||||
float second_move_extrusion(GCodeReader &reader)
|
||||
{
|
||||
float extrusion = -1.f;
|
||||
reader.parse_buffer("G1 X1 E1\nG1 X2 E1\n", [&extrusion](GCodeReader &reader, const GCodeReader::GCodeLine &line) {
|
||||
extrusion = line.dist_E(reader);
|
||||
});
|
||||
return extrusion;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("A G-code reader measures extrusion as relative or absolute as its config says", "[GCodeReader]")
|
||||
{
|
||||
const bool relative = GENERATE(false, true);
|
||||
const float expected = relative ? 1.f : 0.f;
|
||||
|
||||
GCodeConfig config;
|
||||
config.use_relative_e_distances.value = relative;
|
||||
GCodeReader applied;
|
||||
applied.apply_config(config);
|
||||
CHECK(applied.config().use_relative_e_distances.value == relative);
|
||||
CHECK_THAT(second_move_extrusion(applied), Catch::Matchers::WithinAbs(expected, 1e-6));
|
||||
|
||||
DynamicPrintConfig dynamic;
|
||||
dynamic.set_key_value("use_relative_e_distances", new ConfigOptionBool(relative));
|
||||
GCodeReader applied_dynamic;
|
||||
applied_dynamic.apply_config(dynamic);
|
||||
CHECK_THAT(second_move_extrusion(applied_dynamic), Catch::Matchers::WithinAbs(expected, 1e-6));
|
||||
|
||||
GCodeReader copy = applied;
|
||||
CHECK(copy.config().use_relative_e_distances.value == relative);
|
||||
CHECK_THAT(second_move_extrusion(copy), Catch::Matchers::WithinAbs(expected, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A G-code reader without a config uses the default one", "[GCodeReader]")
|
||||
{
|
||||
const bool relative = GCodeConfig().use_relative_e_distances.value;
|
||||
GCodeReader reader;
|
||||
CHECK(reader.config().use_relative_e_distances.value == relative);
|
||||
CHECK_THAT(second_move_extrusion(reader), Catch::Matchers::WithinAbs(relative ? 1.f : 0.f, 1e-6));
|
||||
}
|
||||
@@ -4,8 +4,11 @@
|
||||
#include <locale.h>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
@@ -44,6 +47,34 @@ struct CommaNumericLocale
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Floats print as printf prints them in the C locale", "[LocalesUtils]")
|
||||
{
|
||||
const auto [value, precision, text] = GENERATE(table<double, int, std::string>({
|
||||
{0.5, -1, "0.5"},
|
||||
{25. / 3., -1, "8.33333"},
|
||||
{1500.5, -1, "1500.5"},
|
||||
{1e6, -1, "1e+06"},
|
||||
{-0.000123, -1, "-0.000123"},
|
||||
{25. / 3., 3, "8.333"},
|
||||
{2., 0, "2"},
|
||||
// Longer than the to_chars buffer.
|
||||
{1e21, 2, "1000000000000000000000.00"},
|
||||
}));
|
||||
CHECK(float_to_string_decimal_point(value, precision) == text);
|
||||
}
|
||||
|
||||
TEST_CASE("Floats print with a decimal point in a locale whose decimal separator is a comma", "[LocalesUtils]")
|
||||
{
|
||||
CommaNumericLocale comma;
|
||||
{
|
||||
CNumericLocalesSetter outer;
|
||||
if (! comma.apply())
|
||||
SKIP("no locale with a comma decimal separator is installed");
|
||||
CHECK(float_to_string_decimal_point(1500.5) == "1500.5");
|
||||
CHECK(float_to_string_decimal_point(25. / 3., 3) == "8.333");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("a setter nested in another leaves the C locale in place for the outer one", "[LocalesUtils]")
|
||||
{
|
||||
CNumericLocalesSetter outer;
|
||||
|
||||
Reference in New Issue
Block a user