From 5a1441f9b7b9bb6d35b97ad9cbc25a94c2700d7b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 3 May 2021 16:00:46 +0200 Subject: [PATCH 1/9] Created a RAII class to switch LC_NUMERIC to "C" and back --- src/libslic3r/CMakeLists.txt | 2 ++ src/libslic3r/LocalesUtils.cpp | 59 ++++++++++++++++++++++++++++++++++ src/libslic3r/LocalesUtils.hpp | 47 +++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/libslic3r/LocalesUtils.cpp create mode 100644 src/libslic3r/LocalesUtils.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d98d0e10f..b7b90a8f5 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -126,6 +126,8 @@ add_library(libslic3r STATIC Line.hpp LibraryCheck.cpp LibraryCheck.hpp + LocalesUtils.cpp + LocalesUtils.hpp Model.cpp Model.hpp ModelArrange.hpp diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp new file mode 100644 index 000000000..d98145e4b --- /dev/null +++ b/src/libslic3r/LocalesUtils.cpp @@ -0,0 +1,59 @@ +#include "LocalesUtils.hpp" + + +namespace Slic3r { + + +CNumericLocalesSetter::CNumericLocalesSetter() +{ +#ifdef _WIN32 + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr); + std::setlocale(LC_NUMERIC, "C"); +#else + m_original_locale = uselocale((locale_t)0); + m_new_locale = duplocale(m_original_locale); + m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale); + uselocale(m_new_locale); +#endif +} + + + +CNumericLocalesSetter::~CNumericLocalesSetter() +{ +#ifdef _WIN32 + std::setlocale(LC_NUMERIC, m_orig_numeric_locale) +#else + uselocale(m_original_locale); + freelocale(m_new_locale); +#endif +} + + + +bool is_decimal_separator_point() +{ + char str[5] = ""; + sprintf(str, "%.1f", 0.5f); + return str[1] == '.'; +} + +std::string float_to_string_decimal_point(double value, int precision/* = -1*/) +{ + assert(is_decimal_separator_point()); + std::stringstream buf; + if (precision >= 0) + buf << std::fixed << std::setprecision(precision); + buf << value; + return buf.str(); +} + +std::string float_to_string_decimal_point(float value, int precision/* = -1*/) +{ + return float_to_string_decimal_point(double(value), precision); +} + + +} // namespace Slic3r + diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp new file mode 100644 index 000000000..74281a157 --- /dev/null +++ b/src/libslic3r/LocalesUtils.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_LocalesUtils_hpp_ +#define slic3r_LocalesUtils_hpp_ + +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + +namespace Slic3r { + +// RAII wrapper that sets LC_NUMERIC to "C" on construction +// and restores the old value on destruction. +class CNumericLocalesSetter { +public: + CNumericLocalesSetter(); + ~CNumericLocalesSetter(); + +private: +#ifdef _WIN32 + std::string m_orig_numeric_locale; +#else + locale_t m_original_locale; + locale_t m_new_locale; +#endif + +}; + +// A function to check that current C locale uses decimal point as a separator. +// Intended mostly for asserts. +bool is_decimal_separator_point(); + + +// A substitute for std::to_string that works according to +// C++ locales, not C locale. Meant to be used when we need +// to be sure that decimal point is used as a separator. +// (We use user C locales and "C" C++ locales in most of the code.) +std::string float_to_string_decimal_point(double value, int precision = -1); +std::string float_to_string_decimal_point(float value, int precision = -1); + + +} // namespace Slic3r + +#endif // slic3r_LocalesUtils_hpp_ From 9ee2fc8275eb9074a1abbc1439264f05dcee04be Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 May 2021 14:06:31 +0200 Subject: [PATCH 2/9] Fixed first batch of locale-dependent calls --- src/libslic3r/GCode.cpp | 7 ++- src/libslic3r/GCode/GCodeProcessor.cpp | 5 +- src/libslic3r/GCode/WipeTower.cpp | 78 +++++++++++--------------- src/slic3r/GUI/ConfigWizard.cpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 18 +++--- src/slic3r/GUI/GUI_App.cpp | 2 +- 6 files changed, 52 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d47d185a0..208363f35 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -12,6 +12,7 @@ #include "Utils.hpp" #include "ClipperUtils.hpp" #include "libslic3r.h" +#include "LocalesUtils.hpp" #include #include @@ -750,6 +751,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re std::string path_tmp(path); path_tmp += ".tmp"; + CNumericLocalesSetter c_locales_setter; FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); @@ -981,6 +983,7 @@ namespace DoExport { double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001; auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { + assert(is_decimal_separator_point()); while (dst.second < extruder.id()) { // Fill in the non-printing extruders with zeros. dst.first += (dst.second > 0) ? ", 0" : "0"; @@ -1620,7 +1623,7 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), travel_acc); - + assert(is_decimal_separator_point()); fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config().machine_max_jerk_x.values.front(), print.config().machine_max_jerk_y.values.front(), @@ -1984,6 +1987,7 @@ void GCode::process_layer( } std::string gcode; + assert(is_decimal_separator_point()); // for the sprintfs // add tag for processor #if ENABLE_VALIDATE_CUSTOM_GCODE @@ -2826,6 +2830,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); char buf[64]; + assert(is_decimal_separator_point()); if (path.role() != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = path.role(); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 917f84f40..61c835a4b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/LocalesUtils.hpp" #include "GCodeProcessor.hpp" #include @@ -465,9 +466,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) }; auto format_time_float = [](float time) { - char time_str[64]; - sprintf(time_str, "%.2f", time); - return std::string(time_str); + return Slic3r::float_to_string_decimal_point(time, 2); }; auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index fc6a15b65..5e1937ad8 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -4,9 +4,12 @@ #include #include #include +#include +#include #include "GCodeProcessor.hpp" #include "BoundingBox.hpp" +#include "LocalesUtils.hpp" namespace Slic3r @@ -30,29 +33,27 @@ public: m_filpar(filament_parameters) { // adds tag for analyzer: - char buf[64]; + std::ostringstream str; #if ENABLE_VALIDATE_CUSTOM_GCODE - sprintf(buf, ";%s%f\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming - m_gcode += buf; - sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << ExtrusionEntity::role_to_string(erWipeTower) << "\n"; #else - sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming - m_gcode += buf; - sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); + str << ";" << GCodeProcessor::Height_Tag << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + str << ";" << GCodeProcessor::Extrusion_Role_Tag << ExtrusionEntity::role_to_string(erWipeTower) << "\n"; #endif // ENABLE_VALIDATE_CUSTOM_GCODE - m_gcode += buf; + m_gcode += str.str(); change_analyzer_line_width(line_width); } WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: - char buf[64]; + std::stringstream str; #if ENABLE_VALIDATE_CUSTOM_GCODE - sprintf(buf, ";%s%f\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width).c_str(), line_width); + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) << line_width << "\n"; #else - sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); + str << ";" << GCodeProcessor::Width_Tag << line_width << "\n"; #endif // ENABLE_VALIDATE_CUSTOM_GCODE - m_gcode += buf; + m_gcode += str.str(); return *this; } @@ -61,9 +62,9 @@ public: static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); // adds tag for processor: - char buf[64]; - sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); - m_gcode += buf; + std::stringstream str; + str << ";" << GCodeProcessor::Mm3_Per_Mm_Tag << mm3_per_mm << "\n"; + m_gcode += str.str(); return *this; } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -325,9 +326,7 @@ public: { if (time==0.f) return *this; - char buf[128]; - sprintf(buf, "G4 S%.3f\n", time); - m_gcode += buf; + m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n"; return *this; } @@ -443,37 +442,29 @@ private: const std::vector& m_filpar; std::string set_format_X(float x) - { - char buf[64]; - sprintf(buf, " X%.3f", x); - m_current_pos.x() = x; - return buf; + { + m_current_pos.x() = x; + return " X" + Slic3r::float_to_string_decimal_point(x, 3); } std::string set_format_Y(float y) { - char buf[64]; - sprintf(buf, " Y%.3f", y); - m_current_pos.y() = y; - return buf; + m_current_pos.y() = y; + return " Y" + Slic3r::float_to_string_decimal_point(y, 3); } std::string set_format_Z(float z) { - char buf[64]; - sprintf(buf, " Z%.3f", z); - return buf; + return " Z" + Slic3r::float_to_string_decimal_point(z, 3); } std::string set_format_E(float e) { - char buf[64]; - sprintf(buf, " E%.4f", e); - return buf; + return " E" + Slic3r::float_to_string_decimal_point(e, 4); } std::string set_format_F(float f) { - char buf[64]; - sprintf(buf, " F%d", int(floor(f + 0.5f))); - m_current_feedrate = f; - return buf; + char buf[64]; + sprintf(buf, " F%d", int(floor(f + 0.5f))); + m_current_feedrate = f; + return buf; } WipeTowerWriter& operator=(const WipeTowerWriter &rhs); @@ -959,8 +950,8 @@ void WipeTower::toolchange_Change( // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. Vec2f current_pos = writer.pos_rotated(); writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 - .append(std::string("G1 X") + std::to_string(current_pos.x()) - + " Y" + std::to_string(current_pos.y()) + .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x()) + + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + never_skip_tag() + "\n"); // The toolchange Tn command will be inserted later, only in case that the user does @@ -1310,11 +1301,10 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, { assert(first.new_tool == second.initial_tool); WipeTower::ToolChangeResult out = first; - if (first.end_pos != second.start_pos) { - char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. - sprintf(buf, "G1 X%.3f Y%.3f F7200\n", second.start_pos.x(), second.start_pos.y()); - out.gcode += buf; - } + if (first.end_pos != second.start_pos) + out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + + " F7200\n"; out.gcode += second.gcode; out.extrusions.insert(out.extrusions.end(), second.extrusions.begin(), second.extrusions.end()); out.end_pos = second.end_pos; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 52df39fd5..c35172752 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1457,7 +1457,7 @@ void PageDiameters::apply_custom_config(DynamicPrintConfig &config) config.set_key_value("filament_diameter", opt_filam); auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { - char buf[64]; + char buf[64]; // locales don't matter here (sprintf/atof) sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 209679c0f..f71921bec 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/LocalesUtils.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" #include "Plater.hpp" @@ -78,6 +79,7 @@ static float round_to_nearest(float value, unsigned int decimals) res = std::round(value); else { char buf[64]; + // locales should not matter, both sprintf and stof are sensitive, so... sprintf(buf, "%.*g", decimals, value); res = std::stof(buf); } @@ -3465,18 +3467,16 @@ void GCodeViewer::render_statistics() const ImGuiWrapper& imgui = *wxGetApp().imgui(); auto add_time = [this, &imgui](const std::string& label, int64_t time) { - char buf[1024]; - sprintf(buf, "%lld ms (%s)", time, get_time_dhms(static_cast(time) * 0.001f).c_str()); imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); - imgui.text(buf); + imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); }; auto add_memory = [this, &imgui](const std::string& label, int64_t memory) { - auto format_string = [memory](const std::string& units, float value) { - char buf[1024]; - sprintf(buf, "%lld bytes (%.3f %s)", memory, static_cast(memory) * value, units.c_str()); - return std::string(buf); + auto format_string = [memory](const std::string& units, float value) { + return std::to_string(memory) + " bytes (" + + Slic3r::float_to_string_decimal_point(float(memory) * value, 3) + + " " + units + ")"; }; static const float kb = 1024.0f; @@ -3496,11 +3496,9 @@ void GCodeViewer::render_statistics() const }; auto add_counter = [this, &imgui](const std::string& label, int64_t counter) { - char buf[1024]; - sprintf(buf, "%lld", counter); imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); - imgui.text(buf); + imgui.text(std::to_string(counter)); }; imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 803ab5a14..d6520335b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1586,7 +1586,7 @@ bool GUI_App::load_language(wxString language, bool initial) m_wxLocale->AddCatalog(SLIC3R_APP_KEY); m_imgui->set_language(into_u8(language_info->CanonicalName)); //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. - wxSetlocale(LC_NUMERIC, "C"); + //wxSetlocale(LC_NUMERIC, "C"); Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); return true; } From fef385cd6b948cccb2aa75cb77d0abeb4fcac355 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 May 2021 07:08:38 +0200 Subject: [PATCH 3/9] Fixed second batch of locale-dependent calls --- src/libslic3r/Arrange.cpp | 1 - src/libslic3r/Format/3mf.cpp | 12 +++++ src/libslic3r/Format/AMF.cpp | 53 +++++++++-------------- src/libslic3r/Format/AMF.hpp | 2 +- src/libslic3r/Format/objparser.cpp | 8 ++++ src/libslic3r/GCode/GCodeProcessor.cpp | 8 ++-- src/libslic3r/GCode/PressureEqualizer.cpp | 3 +- src/libslic3r/GCodeReader.cpp | 4 ++ src/libslic3r/LocalesUtils.cpp | 17 ++++++++ src/libslic3r/LocalesUtils.hpp | 2 +- src/slic3r/GUI/Field.cpp | 4 +- src/slic3r/GUI/GUI.cpp | 4 +- src/slic3r/GUI/Selection.cpp | 5 ++- 13 files changed, 78 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 61a32678b..9602ebdbd 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -1,5 +1,4 @@ #include "Arrange.hpp" -#include "SVG.hpp" #include "BoundingBox.hpp" diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 152d72079..b98cc5f04 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2,6 +2,7 @@ #include "../Exception.hpp" #include "../Model.hpp" #include "../Utils.hpp" +#include "../LocalesUtils.hpp" #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" @@ -2408,6 +2409,7 @@ namespace Slic3r { }; auto format_coordinate = [](float f, char *buf) -> char* { + assert(is_decimal_separator_point()); #if EXPORT_3MF_USE_SPIRIT_KARMA_FP // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, // https://github.com/boostorg/spirit/pull/586 @@ -2575,6 +2577,7 @@ namespace Slic3r { bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) { + assert(is_decimal_separator_point()); std::string out = ""; char buffer[1024]; @@ -2666,6 +2669,7 @@ namespace Slic3r { bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { + assert(is_decimal_separator_point()); std::string out = ""; char buffer[1024]; @@ -2700,6 +2704,7 @@ namespace Slic3r { bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) { + assert(is_decimal_separator_point()); const char *const fmt = "object_id=%d|"; std::string out; @@ -2750,6 +2755,7 @@ namespace Slic3r { bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) { + assert(is_decimal_separator_point()); char buffer[1024]; sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; @@ -2926,6 +2932,9 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c if (path == nullptr || config == nullptr || model == nullptr) return false; + // All import should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, *config, check_version); importer.log_errors(); @@ -2934,6 +2943,9 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) { + // All export should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + if (path == nullptr || model == nullptr) return false; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 1c9b6b27d..94318a930 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -15,6 +15,7 @@ #include "../I18N.hpp" #include "../Geometry.hpp" #include "../CustomGCode.hpp" +#include "../LocalesUtils.hpp" #include "AMF.hpp" @@ -498,6 +499,7 @@ void AMFParserContext::characters(const XML_Char *s, int len) void AMFParserContext::endElement(const char * /* name */) { + assert(is_decimal_separator_point()); switch (m_path.back()) { // Constellation transformation: @@ -1052,6 +1054,8 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model // If config is not a null pointer, updates it if the amf file/archive contains config data bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { + CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator + if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output return load_amf_file(path, config, model); @@ -1251,40 +1255,25 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " \n"; if (!object->instances.empty()) { for (ModelInstance *instance : object->instances) { - char buf[512]; - ::sprintf(buf, - " \n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %lf\n" - " %d\n" - " \n", - object_id, - instance->get_offset(X), - instance->get_offset(Y), - instance->get_offset(Z), - instance->get_rotation(X), - instance->get_rotation(Y), - instance->get_rotation(Z), - instance->get_scaling_factor(X), - instance->get_scaling_factor(Y), - instance->get_scaling_factor(Z), - instance->get_mirror(X), - instance->get_mirror(Y), - instance->get_mirror(Z), - instance->printable); + std::stringstream buf; + buf << " \n" + << " " << instance->get_offset(X) << "\n" + << " " << instance->get_offset(Y) << "\n" + << " " << instance->get_offset(Z) << "\n" + << " " << instance->get_rotation(X) << "\n" + << " " << instance->get_rotation(Y) << "\n" + << " " << instance->get_rotation(Z) << "\n" + << " " << instance->get_scaling_factor(X) << "\n" + << " " << instance->get_scaling_factor(Y) << "\n" + << " " << instance->get_scaling_factor(Z) << "\n" + << " " << instance->get_mirror(X) << "\n" + << " " << instance->get_mirror(Y) << "\n" + << " " << instance->get_mirror(Z) << "\n" + << " " << instance->printable << "\n" + << " \n"; //FIXME missing instance->scaling_factor - instances.append(buf); + instances.append(buf.str()); } } } diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index 3e33d4aa3..b4e2c54ab 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -13,6 +13,6 @@ extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, // The model could be modified during the export process if meshes are not repaired or have no shared vertices extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources); -}; // namespace Slic3r +} // namespace Slic3r #endif /* slic3r_Format_AMF_hpp_ */ diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 12cee350d..6cbef7b77 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -6,6 +6,8 @@ #include "objparser.hpp" +#include "libslic3r/LocalesUtils.hpp" + namespace ObjParser { static bool obj_parseline(const char *line, ObjData &data) @@ -15,6 +17,8 @@ static bool obj_parseline(const char *line, ObjData &data) if (*line == 0) return true; + assert(is_decimal_separator_point()); + // Ignore whitespaces at the beginning of the line. //FIXME is this a good idea? EATWS(); @@ -322,6 +326,8 @@ static bool obj_parseline(const char *line, ObjData &data) bool objparse(const char *path, ObjData &data) { + Slic3r::CNumericLocalesSetter locales_setter; + FILE *pFile = boost::nowide::fopen(path, "rt"); if (pFile == 0) return false; @@ -365,6 +371,8 @@ bool objparse(const char *path, ObjData &data) bool objparse(std::istream &stream, ObjData &data) { + Slic3r::CNumericLocalesSetter locales_setter; + try { char buf[65536 * 2]; size_t len = 0; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 61c835a4b..f4b38e2ec 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -670,7 +670,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time)); std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time)); - is_last |= (std::stof(time_float_str) > 0.0f && std::stof(next_time_float_str) == 0.0f); + is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.); } if (is_last) { @@ -1373,7 +1373,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) if (pos != cmt.npos) { pos = cmt.find(',', pos); if (pos != cmt.npos) { - out = std::stod(cmt.substr(pos + 1)); + out = string_to_double_decimal_point(cmt.substr(pos+1)); return true; } } @@ -1523,9 +1523,9 @@ template else if constexpr (std::is_same_v) out = std::stol(str, &read); else if constexpr (std::is_same_v) - out = std::stof(str, &read); + out = string_to_double_decimal_point(str, &read); else if constexpr (std::is_same_v) - out = std::stod(str, &read); + out = string_to_double_decimal_point(str, &read); return str.size() == read; } catch (...) { return false; diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index c3f084a24..48a16a8d5 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -4,6 +4,7 @@ #include "../libslic3r.h" #include "../PrintConfig.hpp" +#include "../LocalesUtils.hpp" #include "PressureEqualizer.hpp" @@ -158,7 +159,7 @@ static inline int parse_int(const char *&line) static inline float parse_float(const char *&line) { char *endptr = NULL; - float result = strtof(line, &endptr); + float result = string_to_double_decimal_point(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); line = endptr; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 9753e4820..5ab1ae50c 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -6,6 +6,8 @@ #include #include +#include "LocalesUtils.hpp" + #include namespace Slic3r { @@ -25,6 +27,7 @@ void GCodeReader::apply_config(const DynamicPrintConfig &config) const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command) { PROFILE_FUNC(); + CNumericLocalesSetter locales_setter; // for strtod // command and args const char *c = ptr; @@ -150,6 +153,7 @@ bool GCodeReader::GCodeLine::has(char axis) const bool GCodeReader::GCodeLine::has_value(char axis, float &value) const { + CNumericLocalesSetter locales_setter; // for strtod const char *c = m_raw.c_str(); // Skip the whitespaces. c = skip_whitespaces(c); diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index d98145e4b..5ee6ac58e 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -1,5 +1,6 @@ #include "LocalesUtils.hpp" +#include namespace Slic3r { @@ -39,6 +40,22 @@ bool is_decimal_separator_point() return str[1] == '.'; } + +double string_to_double_decimal_point(const std::string& str, size_t* pos /* = nullptr*/) +{ + double out; + std::istringstream stream(str); + if (! (stream >> out)) + throw std::invalid_argument("string_to_double_decimal_point conversion failed."); + if (pos) { + if (stream.eof()) + *pos = str.size(); + else + *pos = stream.tellg(); + } + return out; +} + std::string float_to_string_decimal_point(double value, int precision/* = -1*/) { assert(is_decimal_separator_point()); diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index 74281a157..c74c8b1ac 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -40,7 +40,7 @@ bool is_decimal_separator_point(); // (We use user C locales and "C" C++ locales in most of the code.) 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& str, size_t* pos = nullptr); } // namespace Slic3r diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 1fe28eb34..677577054 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1215,8 +1215,8 @@ boost::any& Choice::get_value() return m_value; } -void Choice::enable() { dynamic_cast(window)->Enable(); }; -void Choice::disable() { dynamic_cast(window)->Disable(); }; +void Choice::enable() { dynamic_cast(window)->Enable(); } +void Choice::disable() { dynamic_cast(window)->Disable(); } void Choice::msw_rescale() { diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index f3a8f6ba1..f4dbcfc43 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -2,6 +2,8 @@ #include "GUI_App.hpp" #include "I18N.hpp" +#include "libslic3r/LocalesUtils.hpp" + #include #include @@ -113,7 +115,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt str.pop_back(); percent = true; } - double val = stod(str); + double val = string_to_double_decimal_point(str); config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent)); break;} case coPercent: diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4cf71363d..5c34d7de0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -11,6 +11,7 @@ #include "Camera.hpp" #include "Plater.hpp" +#include "libslic3r/LocalesUtils.hpp" #include "libslic3r/Model.hpp" #if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #include "libslic3r/PresetBundle.hpp" @@ -1927,7 +1928,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - double max_z = std::stod(field.substr(pos + 1)); + double max_z = string_to_double_decimal_point(field.substr(pos + 1)); // extract min_z field = field.substr(0, pos); @@ -1935,7 +1936,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - const double min_z = std::stod(field.substr(pos + 1)); + const double min_z = string_to_double_decimal_point(field.substr(pos + 1)); // extract type field = field.substr(0, pos); From c5c6f51ae0802bf39ef4fe2cf3bd324967e30cb1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 May 2021 08:13:23 +0200 Subject: [PATCH 4/9] Fixed third batch of locale-dependent calls --- src/libslic3r/AppConfig.cpp | 15 +++++++++------ src/libslic3r/AppConfig.hpp | 2 +- src/libslic3r/Config.cpp | 4 +++- src/libslic3r/Config.hpp | 8 ++++---- src/libslic3r/Format/SL1.cpp | 1 + src/libslic3r/LocalesUtils.cpp | 8 ++++---- src/libslic3r/LocalesUtils.hpp | 2 +- src/libslic3r/Point.hpp | 10 ++++++---- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- src/slic3r/GUI/BitmapCache.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- 11 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index d9301d1f3..cf532160d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -2,8 +2,10 @@ #include "libslic3r/Utils.hpp" #include "AppConfig.hpp" #include "Exception.hpp" +#include "LocalesUtils.hpp" #include "Thread.hpp" + #include #include #include @@ -376,7 +378,8 @@ void AppConfig::set_recent_projects(const std::vector& recent_proje } } -void AppConfig::set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone, double zoom_speed, bool swap_yz) +void AppConfig::set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, + float rotation_speed, float rotation_deadzone, double zoom_speed, bool swap_yz) { std::string key = std::string("mouse_device:") + name; auto it = m_storage.find(key); @@ -384,11 +387,11 @@ void AppConfig::set_mouse_device(const std::string& name, double translation_spe it = m_storage.insert(std::map>::value_type(key, std::map())).first; it->second.clear(); - it->second["translation_speed"] = std::to_string(translation_speed); - it->second["translation_deadzone"] = std::to_string(translation_deadzone); - it->second["rotation_speed"] = std::to_string(rotation_speed); - it->second["rotation_deadzone"] = std::to_string(rotation_deadzone); - it->second["zoom_speed"] = std::to_string(zoom_speed); + it->second["translation_speed"] = float_to_string_decimal_point(translation_speed); + it->second["translation_deadzone"] = float_to_string_decimal_point(translation_deadzone); + it->second["rotation_speed"] = float_to_string_decimal_point(rotation_speed); + it->second["rotation_deadzone"] = float_to_string_decimal_point(rotation_deadzone); + it->second["zoom_speed"] = float_to_string_decimal_point(zoom_speed); it->second["swap_yz"] = swap_yz ? "1" : "0"; } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index c8ccd18cd..03924175d 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -196,6 +196,6 @@ private: bool m_legacy_datadir; }; -}; // namespace Slic3r +} // namespace Slic3r #endif /* slic3r_AppConfig_hpp_ */ diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 5db1d8179..bd396243c 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1,6 +1,8 @@ #include "Config.hpp" #include "format.hpp" #include "Utils.hpp" +#include "LocalesUtils.hpp" + #include #include #include @@ -462,7 +464,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create) switch (opt->type()) { case coFloat: static_cast(opt)->value = value; break; case coFloatOrPercent: static_cast(opt)->value = value; static_cast(opt)->percent = false; break; - case coString: static_cast(opt)->value = std::to_string(value); break; + case coString: static_cast(opt)->value = float_to_string_decimal_point(value); break; default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible"); } } diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index adda2654e..e67b6b781 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1819,10 +1819,10 @@ public: SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} - SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} - SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} - SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} - SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} std::string opt_key; std::string opt_value; bool append = false; }; // May throw BadOptionTypeException() if the operation fails. diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 4038cb046..1809c3e4f 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -345,6 +345,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) void fill_iniconf(ConfMap &m, const SLAPrint &print) { + CNumericLocalesSetter locales_setter; // for to_string auto &cfg = print.full_print_config(); m["layerHeight"] = get_cfg_value(cfg, "layer_height"); m["expTime"] = get_cfg_value(cfg, "exposure_time"); diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index 5ee6ac58e..116078a25 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -66,10 +66,10 @@ std::string float_to_string_decimal_point(double value, int precision/* = -1*/) return buf.str(); } -std::string float_to_string_decimal_point(float value, int precision/* = -1*/) -{ - return float_to_string_decimal_point(double(value), precision); -} +//std::string float_to_string_decimal_point(float value, int precision/* = -1*/) +//{ +// return float_to_string_decimal_point(double(value), precision); +//} } // namespace Slic3r diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index c74c8b1ac..113cfa04b 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -39,7 +39,7 @@ bool is_decimal_separator_point(); // to be sure that decimal point is used as a separator. // (We use user C locales and "C" C++ locales in most of the code.) std::string float_to_string_decimal_point(double value, int precision = -1); -std::string float_to_string_decimal_point(float 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& str, size_t* pos = nullptr); } // namespace Slic3r diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 12870b713..e5ce68420 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -11,6 +11,8 @@ #include +#include "LocalesUtils.hpp" + namespace Slic3r { class BoundingBox; @@ -88,10 +90,10 @@ inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); } inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); } -inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; } -inline std::string to_string(const Vec2d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; } -inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; } -inline std::string to_string(const Vec3d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; } +inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt(0)) + ", " + float_to_string_decimal_point(pt(1)) + "]"; } +inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt(0)) + ", " + float_to_string_decimal_point(pt(1)) + "]"; } +inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt(0)) + ", " + float_to_string_decimal_point(pt(1)) + ", " + float_to_string_decimal_point(pt(2)) + "]"; } +inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt(0)) + ", " + float_to_string_decimal_point(pt(1)) + ", " + float_to_string_decimal_point(pt(2)) + "]"; } std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 46064c55b..51e2430aa 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -166,8 +166,8 @@ struct FaceHash { Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; // Return a concatenated string representation of the coordinates - return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); - }; + return float_to_string_decimal_point(c(0)) + float_to_string_decimal_point(c(1)) + float_to_string_decimal_point(c(2)); + } FaceHash(const indexed_triangle_set &its) { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 0a6f3e4f9..2c756c3b9 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -266,7 +266,7 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ std::string bitmap_key = bitmap_name + ( target_height !=0 ? "-h" + std::to_string(target_height) : "-w" + std::to_string(target_width)) - + (m_scale != 1.0f ? "-s" + std::to_string(m_scale) : "") + + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + (grayscale ? "-gs" : ""); /* For the Dark mode of any platform, we should draw icons in respect to OS background diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 734bc24aa..455eb8a78 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3800,7 +3800,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { settings.distance = std::max(dist_min, settings.distance); settings_out.distance = settings.distance; - appcfg->set("arrange", dist_key.c_str(), std::to_string(settings_out.distance)); + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); settings_changed = true; } @@ -3815,7 +3815,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) if (imgui->button(_L("Reset"))) { settings_out = ArrangeSettings{}; settings_out.distance = std::max(dist_min, settings_out.distance); - appcfg->set("arrange", dist_key.c_str(), std::to_string(settings_out.distance)); + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); settings_changed = true; } From 6b6b0869485ab0c130b3a6cf9c03fa42e291c7d9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 May 2021 12:18:30 +0200 Subject: [PATCH 5/9] Fixed tests and windows build --- src/libslic3r/LocalesUtils.cpp | 2 +- tests/libslic3r/test_config.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index 116078a25..cb118f99c 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -24,7 +24,7 @@ CNumericLocalesSetter::CNumericLocalesSetter() CNumericLocalesSetter::~CNumericLocalesSetter() { #ifdef _WIN32 - std::setlocale(LC_NUMERIC, m_orig_numeric_locale) + std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data()); #else uselocale(m_original_locale); freelocale(m_new_locale); diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index c3f17def2..417a29dca 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/LocalesUtils.hpp" using namespace Slic3r; @@ -112,7 +113,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { WHEN("A string option is set through the double interface") { config.set("end_gcode", 100.5); THEN("The underlying value is set correctly.") { - REQUIRE(config.opt("end_gcode")->value == std::to_string(100.5)); + REQUIRE(config.opt("end_gcode")->value == float_to_string_decimal_point(100.5)); } } WHEN("A float or percent is set as a percent through the string interface.") { From 4a7f07852741d8109d74b67eddaa7d3027028301 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 May 2021 16:40:00 +0200 Subject: [PATCH 6/9] Fixed debug build --- src/libslic3r/Format/objparser.cpp | 2 +- src/libslic3r/GCode/CoolingBuffer.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 6cbef7b77..16a3f84dd 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -17,7 +17,7 @@ static bool obj_parseline(const char *line, ObjData &data) if (*line == 0) return true; - assert(is_decimal_separator_point()); + assert(Slic3r::is_decimal_separator_point()); // Ignore whitespaces at the beginning of the line. //FIXME is this a good idea? diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 07b9b07bb..6dd5e7e27 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -357,6 +357,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: for (; *c == ' ' || *c == '\t'; ++ c); if (*c == 0 || *c == ';') break; + + assert(is_decimal_separator_point()); // for atof // Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); @@ -454,6 +456,7 @@ std::vector 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.); From c05b8210f24c6d127778a8762ba1d5af1411e7fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 24 May 2021 12:20:55 +0200 Subject: [PATCH 7/9] Fixed several locales issues on file import/export --- src/admesh/shared.cpp | 6 +++++- src/admesh/stlinit.cpp | 3 +++ src/slic3r/GUI/GCodeViewer.cpp | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 902bbfc9b..a8fbc2a87 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -30,6 +30,8 @@ #include "stl.h" +#include "libslic3r/LocalesUtils.hpp" + void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) { // 3 indices to vertex per face @@ -127,6 +129,7 @@ void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) bool its_write_off(const indexed_triangle_set &its, const char *file) { + Slic3r::CNumericLocalesSetter locales_setter; /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { @@ -146,6 +149,7 @@ bool its_write_off(const indexed_triangle_set &its, const char *file) bool its_write_vrml(const indexed_triangle_set &its, const char *file) { + Slic3r::CNumericLocalesSetter locales_setter; /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { @@ -188,7 +192,7 @@ bool its_write_vrml(const indexed_triangle_set &its, const char *file) bool its_write_obj(const indexed_triangle_set &its, const char *file) { - + Slic3r::CNumericLocalesSetter locales_setter; FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 6aa2c4417..29131b1c7 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -32,6 +32,8 @@ #include "stl.h" +#include "libslic3r/LocalesUtils.hpp" + #ifndef SEEK_SET #error "SEEK_SET not defined" #endif @@ -232,6 +234,7 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) bool stl_open(stl_file *stl, const char *file) { + Slic3r::CNumericLocalesSetter locales_setter; stl->clear(); FILE *fp = stl_open_count_facets(stl, file); if (fp == nullptr) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index f71921bec..285f07051 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -992,6 +992,9 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const // save materials file boost::filesystem::path mat_filename(filename); mat_filename.replace_extension("mtl"); + + CNumericLocalesSetter locales_setter; + FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; From 4960b125c54ca4ae59b9143116c6eaed884cca26 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 24 May 2021 12:22:46 +0200 Subject: [PATCH 8/9] Fixed incorrect locales handling in the UI (Field, ObjectManipulation, etc) --- src/slic3r/GUI/ConfigWizard.cpp | 16 ++++++----- src/slic3r/GUI/DoubleSlider.cpp | 4 +-- src/slic3r/GUI/ExtruderSequenceDialog.cpp | 9 +++++-- src/slic3r/GUI/Field.cpp | 33 +++++++++++++++-------- src/slic3r/GUI/GUI_ObjectLayers.cpp | 10 ++++--- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++++++--- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index c35172752..f7d16d843 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1388,17 +1388,21 @@ static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) { e.Skip(); wxString str = ctrl->GetValue(); - // Replace the first occurence of comma in decimal number. - bool was_replace = str.Replace(",", ".", false) > 0; + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + double val = 0.0; - if (!str.ToCDouble(&val)) { + if (!str.ToDouble(&val)) { if (val == 0.0) val = def_value; ctrl->SetValue(double_to_string(val)); show_error(nullptr, _L("Invalid numeric input.")); ctrl->SetFocus(); } - else if (was_replace) + else if (was_replaced) ctrl->SetValue(double_to_string(val)); } @@ -1447,12 +1451,12 @@ PageDiameters::PageDiameters(ConfigWizard *parent) void PageDiameters::apply_custom_config(DynamicPrintConfig &config) { double val = 0.0; - diam_nozzle->GetValue().ToCDouble(&val); + diam_nozzle->GetValue().ToDouble(&val); auto *opt_nozzle = new ConfigOptionFloats(1, val); config.set_key_value("nozzle_diameter", opt_nozzle); val = 0.0; - diam_filam->GetValue().ToCDouble(&val); + diam_filam->GetValue().ToDouble(&val); auto * opt_filam = new ConfigOptionFloats(1, val); config.set_key_value("filament_diameter", opt_filam); diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 6d41e3c82..9c280dee6 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2162,7 +2162,7 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, bool disable = textctrl->IsEmpty(); if (!disable && min >= 0.0 && max >= 0.0) { double value = -1.0; - if (!textctrl->GetValue().ToCDouble(&value)) // input value couldn't be converted to double + if (!textctrl->GetValue().ToDouble(&value)) // input value couldn't be converted to double disable = true; else disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ? @@ -2231,7 +2231,7 @@ static double get_value_to_jump(double active_value, double min_z, double max_z, return -1.0; double value = -1.0; - return dlg.GetValue().ToCDouble(&value) ? value : -1.0; + return dlg.GetValue().ToDouble(&value) ? value : -1.0; } void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/) diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.cpp b/src/slic3r/GUI/ExtruderSequenceDialog.cpp index e505f1470..8ae5e9f32 100644 --- a/src/slic3r/GUI/ExtruderSequenceDialog.cpp +++ b/src/slic3r/GUI/ExtruderSequenceDialog.cpp @@ -98,9 +98,14 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ return; } - str.Replace(",", ".", false); + char dec_sep = '.'; + if (! is_decimal_separator_point()) { + str.Replace(".", ",", false); + dec_sep = ','; + } + double val; - if (str == "." || !str.ToCDouble(&val) || val <= 0.0) + if (str == dec_sep || !str.ToDouble(&val) || val <= 0.0) val = 3.0; // default value if (fabs(m_sequence.interval_by_layers - val) < 0.001) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 677577054..de8d3c104 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -37,12 +37,13 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) // with the exception that here one sets the decimal separator explicitely to dot. // If number is in scientific format, trailing zeroes belong to the exponent and cannot be removed. if (s.find_first_of("eE") == wxString::npos) { - const size_t posDecSep = s.find("."); + char dec_sep = is_decimal_separator_point() ? '.' : ','; + const size_t posDecSep = s.find(dec_sep); // No decimal point => removing trailing zeroes irrelevant for integer number. if (posDecSep != wxString::npos) { // Find the last character to keep. size_t posLastNonZero = s.find_last_not_of("0"); - // If it's the decimal separator itself, don't keep it neither. + // If it's the decimal separator itself, don't keep it either. if (posLastNonZero == posDecSep) -- posLastNonZero; s.erase(posLastNonZero + 1); @@ -236,16 +237,22 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true m_value = double(m_opt.min); break; } - double val; - // Replace the first occurence of comma in decimal number. - str.Replace(",", ".", false); - if (str == ".") + double val; + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + set_value(str, false); + + + if (str == dec_sep) val = 0.0; else { if (m_opt.nullable && str == na_value()) val = ConfigOptionFloatsNullable::nil_value(); - else if (!str.ToCDouble(&val)) + else if (!str.ToDouble(&val)) { if (!check_value) { m_value.clear(); @@ -293,14 +300,18 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%') { double val = 0.; - // Replace the first occurence of comma in decimal number. - str.Replace(",", ".", false); + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + set_value(str, false); + // remove space and "mm" substring, if any exists str.Replace(" ", "", true); str.Replace("m", "", true); - if (!str.ToCDouble(&val)) + if (!str.ToDouble(&val)) { if (!check_value) { m_value.clear(); @@ -1478,7 +1489,7 @@ boost::any& PointCtrl::get_value() !y_textctrl->GetValue().ToDouble(&y)) { set_value(m_value.empty() ? Vec2d(0.0, 0.0) : m_value, true); - show_error(m_parent, _L("Invalid numeric input.")); + show_error(m_parent, _L("Invalid numeric input.")); } else if (m_opt.min > x || x > m_opt.max || diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index d079a6ebd..7c13c20d6 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -397,12 +397,16 @@ coordf_t LayerRangeEditor::get_value() wxString str = GetValue(); coordf_t layer_height; - // Replace the first occurence of comma in decimal number. - str.Replace(",", ".", false); + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + SetValue(str); + if (str == ".") layer_height = 0.0; else { - if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) { + if (!str.ToDouble(&layer_height) || layer_height < 0.0f) { show_error(m_parent, _L("Invalid numeric input.")); SetValue(double_to_string(layer_height)); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 095a926ad..cdffbc88d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1095,15 +1095,19 @@ double ManipulationEditor::get_value() wxString str = GetValue(); double value; - // Replace the first occurence of comma in decimal number. - str.Replace(",", ".", false); + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + SetValue(str); + if (str == ".") value = 0.0; - if ((str.IsEmpty() || !str.ToCDouble(&value)) && !m_valid_value.IsEmpty()) { + if ((str.IsEmpty() || !str.ToDouble(&value)) && !m_valid_value.IsEmpty()) { str = m_valid_value; SetValue(str); - str.ToCDouble(&value); + str.ToDouble(&value); } return value; From afca744d37f1c6fc38a9e143b9d65419f1ae8c2b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 24 May 2021 09:33:27 +0200 Subject: [PATCH 9/9] Fixed a bug when reverting to an old value with comma-separated doubles --- src/slic3r/GUI/GUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index f4dbcfc43..c6cb05fac 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -115,7 +115,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt str.pop_back(); percent = true; } - double val = string_to_double_decimal_point(str); + double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field) config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent)); break;} case coPercent: