Merge branch 'lm_clocales'

This commit is contained in:
Lukas Matena 2021-05-24 12:24:29 +02:00
commit 429eda5666
36 changed files with 338 additions and 155 deletions

View File

@ -30,6 +30,8 @@
#include "stl.h" #include "stl.h"
#include "libslic3r/LocalesUtils.hpp"
void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its)
{ {
// 3 indices to vertex per face // 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) bool its_write_off(const indexed_triangle_set &its, const char *file)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
/* Open the file */ /* Open the file */
FILE *fp = boost::nowide::fopen(file, "w"); FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) { 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) bool its_write_vrml(const indexed_triangle_set &its, const char *file)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
/* Open the file */ /* Open the file */
FILE *fp = boost::nowide::fopen(file, "w"); FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) { 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) bool its_write_obj(const indexed_triangle_set &its, const char *file)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
FILE *fp = boost::nowide::fopen(file, "w"); FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) { if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";

View File

@ -32,6 +32,8 @@
#include "stl.h" #include "stl.h"
#include "libslic3r/LocalesUtils.hpp"
#ifndef SEEK_SET #ifndef SEEK_SET
#error "SEEK_SET not defined" #error "SEEK_SET not defined"
#endif #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) bool stl_open(stl_file *stl, const char *file)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
stl->clear(); stl->clear();
FILE *fp = stl_open_count_facets(stl, file); FILE *fp = stl_open_count_facets(stl, file);
if (fp == nullptr) if (fp == nullptr)

View File

@ -2,8 +2,10 @@
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "Exception.hpp" #include "Exception.hpp"
#include "LocalesUtils.hpp"
#include "Thread.hpp" #include "Thread.hpp"
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
@ -376,7 +378,8 @@ void AppConfig::set_recent_projects(const std::vector<std::string>& 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; std::string key = std::string("mouse_device:") + name;
auto it = m_storage.find(key); 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<std::string, std::map<std::string, std::string>>::value_type(key, std::map<std::string, std::string>())).first; it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type(key, std::map<std::string, std::string>())).first;
it->second.clear(); it->second.clear();
it->second["translation_speed"] = std::to_string(translation_speed); it->second["translation_speed"] = float_to_string_decimal_point(translation_speed);
it->second["translation_deadzone"] = std::to_string(translation_deadzone); it->second["translation_deadzone"] = float_to_string_decimal_point(translation_deadzone);
it->second["rotation_speed"] = std::to_string(rotation_speed); it->second["rotation_speed"] = float_to_string_decimal_point(rotation_speed);
it->second["rotation_deadzone"] = std::to_string(rotation_deadzone); it->second["rotation_deadzone"] = float_to_string_decimal_point(rotation_deadzone);
it->second["zoom_speed"] = std::to_string(zoom_speed); it->second["zoom_speed"] = float_to_string_decimal_point(zoom_speed);
it->second["swap_yz"] = swap_yz ? "1" : "0"; it->second["swap_yz"] = swap_yz ? "1" : "0";
} }

View File

@ -196,6 +196,6 @@ private:
bool m_legacy_datadir; bool m_legacy_datadir;
}; };
}; // namespace Slic3r } // namespace Slic3r
#endif /* slic3r_AppConfig_hpp_ */ #endif /* slic3r_AppConfig_hpp_ */

View File

@ -1,5 +1,4 @@
#include "Arrange.hpp" #include "Arrange.hpp"
#include "SVG.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"

View File

@ -126,6 +126,8 @@ add_library(libslic3r STATIC
Line.hpp Line.hpp
LibraryCheck.cpp LibraryCheck.cpp
LibraryCheck.hpp LibraryCheck.hpp
LocalesUtils.cpp
LocalesUtils.hpp
Model.cpp Model.cpp
Model.hpp Model.hpp
ModelArrange.hpp ModelArrange.hpp

View File

@ -1,6 +1,8 @@
#include "Config.hpp" #include "Config.hpp"
#include "format.hpp" #include "format.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "LocalesUtils.hpp"
#include <assert.h> #include <assert.h>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@ -462,7 +464,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create)
switch (opt->type()) { switch (opt->type()) {
case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break; case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break; case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break; case coString: static_cast<ConfigOptionString*>(opt)->value = float_to_string_decimal_point(value); break;
default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible"); default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible");
} }
} }

View File

@ -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 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 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 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 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(std::to_string(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(std::to_string(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(std::to_string(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; std::string opt_key; std::string opt_value; bool append = false;
}; };
// May throw BadOptionTypeException() if the operation fails. // May throw BadOptionTypeException() if the operation fails.

View File

@ -2,6 +2,7 @@
#include "../Exception.hpp" #include "../Exception.hpp"
#include "../Model.hpp" #include "../Model.hpp"
#include "../Utils.hpp" #include "../Utils.hpp"
#include "../LocalesUtils.hpp"
#include "../GCode.hpp" #include "../GCode.hpp"
#include "../Geometry.hpp" #include "../Geometry.hpp"
#include "../GCode/ThumbnailData.hpp" #include "../GCode/ThumbnailData.hpp"
@ -2408,6 +2409,7 @@ namespace Slic3r {
}; };
auto format_coordinate = [](float f, char *buf) -> char* { auto format_coordinate = [](float f, char *buf) -> char* {
assert(is_decimal_separator_point());
#if EXPORT_3MF_USE_SPIRIT_KARMA_FP #if EXPORT_3MF_USE_SPIRIT_KARMA_FP
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
// https://github.com/boostorg/spirit/pull/586 // 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) bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{ {
assert(is_decimal_separator_point());
std::string out = ""; std::string out = "";
char buffer[1024]; 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) bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model)
{ {
assert(is_decimal_separator_point());
std::string out = ""; std::string out = "";
char buffer[1024]; 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) 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|"; const char *const fmt = "object_id=%d|";
std::string out; 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) bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
{ {
assert(is_decimal_separator_point());
char buffer[1024]; char buffer[1024];
sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
std::string out = buffer; 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) if (path == nullptr || config == nullptr || model == nullptr)
return false; return false;
// All import should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter;
_3MF_Importer importer; _3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *config, check_version); bool res = importer.load_model_from_file(path, *model, *config, check_version);
importer.log_errors(); 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) 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) if (path == nullptr || model == nullptr)
return false; return false;

View File

@ -15,6 +15,7 @@
#include "../I18N.hpp" #include "../I18N.hpp"
#include "../Geometry.hpp" #include "../Geometry.hpp"
#include "../CustomGCode.hpp" #include "../CustomGCode.hpp"
#include "../LocalesUtils.hpp"
#include "AMF.hpp" #include "AMF.hpp"
@ -498,6 +499,7 @@ void AMFParserContext::characters(const XML_Char *s, int len)
void AMFParserContext::endElement(const char * /* name */) void AMFParserContext::endElement(const char * /* name */)
{ {
assert(is_decimal_separator_point());
switch (m_path.back()) { switch (m_path.back()) {
// Constellation transformation: // 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 // 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) 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")) if (boost::iends_with(path, ".amf.xml"))
// backward compatibility with older slic3r output // backward compatibility with older slic3r output
return load_amf_file(path, config, model); return load_amf_file(path, config, model);
@ -1251,40 +1255,25 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
stream << " </object>\n"; stream << " </object>\n";
if (!object->instances.empty()) { if (!object->instances.empty()) {
for (ModelInstance *instance : object->instances) { for (ModelInstance *instance : object->instances) {
char buf[512]; std::stringstream buf;
::sprintf(buf, buf << " <instance objectid=\"" << object_id << "\">\n"
" <instance objectid=\"%zu\">\n" << " <deltax>" << instance->get_offset(X) << "</deltax>\n"
" <deltax>%lf</deltax>\n" << " <deltay>" << instance->get_offset(Y) << "</deltay>\n"
" <deltay>%lf</deltay>\n" << " <deltaz>" << instance->get_offset(Z) << "</deltaz>\n"
" <deltaz>%lf</deltaz>\n" << " <rx>" << instance->get_rotation(X) << "</rx>\n"
" <rx>%lf</rx>\n" << " <ry>" << instance->get_rotation(Y) << "</ry>\n"
" <ry>%lf</ry>\n" << " <rz>" << instance->get_rotation(Z) << "</rz>\n"
" <rz>%lf</rz>\n" << " <scalex>" << instance->get_scaling_factor(X) << "</scalex>\n"
" <scalex>%lf</scalex>\n" << " <scaley>" << instance->get_scaling_factor(Y) << "</scaley>\n"
" <scaley>%lf</scaley>\n" << " <scalez>" << instance->get_scaling_factor(Z) << "</scalez>\n"
" <scalez>%lf</scalez>\n" << " <mirrorx>" << instance->get_mirror(X) << "</mirrorx>\n"
" <mirrorx>%lf</mirrorx>\n" << " <mirrory>" << instance->get_mirror(Y) << "</mirrory>\n"
" <mirrory>%lf</mirrory>\n" << " <mirrorz>" << instance->get_mirror(Z) << "</mirrorz>\n"
" <mirrorz>%lf</mirrorz>\n" << " <printable>" << instance->printable << "</printable>\n"
" <printable>%d</printable>\n" << " </instance>\n";
" </instance>\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);
//FIXME missing instance->scaling_factor //FIXME missing instance->scaling_factor
instances.append(buf); instances.append(buf.str());
} }
} }
} }

View File

@ -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 // 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); extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
}; // namespace Slic3r } // namespace Slic3r
#endif /* slic3r_Format_AMF_hpp_ */ #endif /* slic3r_Format_AMF_hpp_ */

View File

@ -345,6 +345,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
void fill_iniconf(ConfMap &m, const SLAPrint &print) void fill_iniconf(ConfMap &m, const SLAPrint &print)
{ {
CNumericLocalesSetter locales_setter; // for to_string
auto &cfg = print.full_print_config(); auto &cfg = print.full_print_config();
m["layerHeight"] = get_cfg_value(cfg, "layer_height"); m["layerHeight"] = get_cfg_value(cfg, "layer_height");
m["expTime"] = get_cfg_value(cfg, "exposure_time"); m["expTime"] = get_cfg_value(cfg, "exposure_time");

View File

@ -6,6 +6,8 @@
#include "objparser.hpp" #include "objparser.hpp"
#include "libslic3r/LocalesUtils.hpp"
namespace ObjParser { namespace ObjParser {
static bool obj_parseline(const char *line, ObjData &data) 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) if (*line == 0)
return true; return true;
assert(Slic3r::is_decimal_separator_point());
// Ignore whitespaces at the beginning of the line. // Ignore whitespaces at the beginning of the line.
//FIXME is this a good idea? //FIXME is this a good idea?
EATWS(); EATWS();
@ -322,6 +326,8 @@ static bool obj_parseline(const char *line, ObjData &data)
bool objparse(const char *path, ObjData &data) bool objparse(const char *path, ObjData &data)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
FILE *pFile = boost::nowide::fopen(path, "rt"); FILE *pFile = boost::nowide::fopen(path, "rt");
if (pFile == 0) if (pFile == 0)
return false; return false;
@ -365,6 +371,8 @@ bool objparse(const char *path, ObjData &data)
bool objparse(std::istream &stream, ObjData &data) bool objparse(std::istream &stream, ObjData &data)
{ {
Slic3r::CNumericLocalesSetter locales_setter;
try { try {
char buf[65536 * 2]; char buf[65536 * 2];
size_t len = 0; size_t len = 0;

View File

@ -12,6 +12,7 @@
#include "Utils.hpp" #include "Utils.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "LocalesUtils.hpp"
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
@ -750,6 +751,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re
std::string path_tmp(path); std::string path_tmp(path);
path_tmp += ".tmp"; path_tmp += ".tmp";
CNumericLocalesSetter c_locales_setter;
FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (file == nullptr) if (file == nullptr)
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); 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_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) { auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
assert(is_decimal_separator_point());
while (dst.second < extruder.id()) { while (dst.second < extruder.id()) {
// Fill in the non-printing extruders with zeros. // Fill in the non-printing extruders with zeros.
dst.first += (dst.second > 0) ? ", 0" : "0"; 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), int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
travel_acc); 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", 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_x.values.front(),
print.config().machine_max_jerk_y.values.front(), print.config().machine_max_jerk_y.values.front(),
@ -1984,6 +1987,7 @@ void GCode::process_layer(
} }
std::string gcode; std::string gcode;
assert(is_decimal_separator_point()); // for the sprintfs
// add tag for processor // add tag for processor
#if ENABLE_VALIDATE_CUSTOM_GCODE #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 // 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); bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower);
char buf[64]; char buf[64];
assert(is_decimal_separator_point());
if (path.role() != m_last_processor_extrusion_role) { if (path.role() != m_last_processor_extrusion_role) {
m_last_processor_extrusion_role = path.role(); m_last_processor_extrusion_role = path.role();

View File

@ -357,6 +357,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
for (; *c == ' ' || *c == '\t'; ++ c); for (; *c == ' ' || *c == '\t'; ++ c);
if (*c == 0 || *c == ';') if (*c == 0 || *c == ';')
break; break;
assert(is_decimal_separator_point()); // for atof
// Parse the axis. // Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
@ -454,6 +456,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_G4; line.type = CoolingLine::TYPE_G4;
size_t pos_S = sline.find('S', 3); size_t pos_S = sline.find('S', 3);
size_t pos_P = sline.find('P', 3); size_t pos_P = sline.find('P', 3);
assert(is_decimal_separator_point()); // for atof
line.time = line.time_max = float( line.time = line.time_max = float(
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);

View File

@ -1,6 +1,7 @@
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "GCodeProcessor.hpp" #include "GCodeProcessor.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -465,9 +466,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
}; };
auto format_time_float = [](float time) { auto format_time_float = [](float time) {
char time_str[64]; return Slic3r::float_to_string_decimal_point(time, 2);
sprintf(time_str, "%.2f", time);
return std::string(time_str);
}; };
auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) { auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) {
@ -671,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 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)); 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) { if (is_last) {
@ -1374,7 +1373,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
if (pos != cmt.npos) { if (pos != cmt.npos) {
pos = cmt.find(',', pos); pos = cmt.find(',', pos);
if (pos != cmt.npos) { if (pos != cmt.npos) {
out = std::stod(cmt.substr(pos + 1)); out = string_to_double_decimal_point(cmt.substr(pos+1));
return true; return true;
} }
} }
@ -1524,9 +1523,9 @@ template<typename T>
else if constexpr (std::is_same_v<T, long>) else if constexpr (std::is_same_v<T, long>)
out = std::stol(str, &read); out = std::stol(str, &read);
else if constexpr (std::is_same_v<T, float>) else if constexpr (std::is_same_v<T, float>)
out = std::stof(str, &read); out = string_to_double_decimal_point(str, &read);
else if constexpr (std::is_same_v<T, double>) else if constexpr (std::is_same_v<T, double>)
out = std::stod(str, &read); out = string_to_double_decimal_point(str, &read);
return str.size() == read; return str.size() == read;
} catch (...) { } catch (...) {
return false; return false;

View File

@ -4,6 +4,7 @@
#include "../libslic3r.h" #include "../libslic3r.h"
#include "../PrintConfig.hpp" #include "../PrintConfig.hpp"
#include "../LocalesUtils.hpp"
#include "PressureEqualizer.hpp" #include "PressureEqualizer.hpp"
@ -158,7 +159,7 @@ static inline int parse_int(const char *&line)
static inline float parse_float(const char *&line) static inline float parse_float(const char *&line)
{ {
char *endptr = NULL; 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)) if (endptr == NULL || !is_ws_or_eol(*endptr))
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
line = endptr; line = endptr;

View File

@ -4,9 +4,12 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <numeric> #include <numeric>
#include <sstream>
#include <iomanip>
#include "GCodeProcessor.hpp" #include "GCodeProcessor.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "LocalesUtils.hpp"
namespace Slic3r namespace Slic3r
@ -30,29 +33,27 @@ public:
m_filpar(filament_parameters) m_filpar(filament_parameters)
{ {
// adds tag for analyzer: // adds tag for analyzer:
char buf[64]; std::ostringstream str;
#if ENABLE_VALIDATE_CUSTOM_GCODE #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 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
m_gcode += buf; str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << ExtrusionEntity::role_to_string(erWipeTower) << "\n";
sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str());
#else #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 str << ";" << GCodeProcessor::Height_Tag << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
m_gcode += buf; str << ";" << GCodeProcessor::Extrusion_Role_Tag << ExtrusionEntity::role_to_string(erWipeTower) << "\n";
sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str());
#endif // ENABLE_VALIDATE_CUSTOM_GCODE #endif // ENABLE_VALIDATE_CUSTOM_GCODE
m_gcode += buf; m_gcode += str.str();
change_analyzer_line_width(line_width); change_analyzer_line_width(line_width);
} }
WipeTowerWriter& change_analyzer_line_width(float line_width) { WipeTowerWriter& change_analyzer_line_width(float line_width) {
// adds tag for analyzer: // adds tag for analyzer:
char buf[64]; std::stringstream str;
#if ENABLE_VALIDATE_CUSTOM_GCODE #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 #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 #endif // ENABLE_VALIDATE_CUSTOM_GCODE
m_gcode += buf; m_gcode += str.str();
return *this; return *this;
} }
@ -61,9 +62,9 @@ public:
static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; 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); float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
// adds tag for processor: // adds tag for processor:
char buf[64]; std::stringstream str;
sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); str << ";" << GCodeProcessor::Mm3_Per_Mm_Tag << mm3_per_mm << "\n";
m_gcode += buf; m_gcode += str.str();
return *this; return *this;
} }
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
@ -325,9 +326,7 @@ public:
{ {
if (time==0.f) if (time==0.f)
return *this; return *this;
char buf[128]; m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
sprintf(buf, "G4 S%.3f\n", time);
m_gcode += buf;
return *this; return *this;
} }
@ -443,37 +442,29 @@ private:
const std::vector<WipeTower::FilamentParameters>& m_filpar; const std::vector<WipeTower::FilamentParameters>& m_filpar;
std::string set_format_X(float x) std::string set_format_X(float x)
{ {
char buf[64]; m_current_pos.x() = x;
sprintf(buf, " X%.3f", x); return " X" + Slic3r::float_to_string_decimal_point(x, 3);
m_current_pos.x() = x;
return buf;
} }
std::string set_format_Y(float y) { std::string set_format_Y(float y) {
char buf[64]; m_current_pos.y() = y;
sprintf(buf, " Y%.3f", y); return " Y" + Slic3r::float_to_string_decimal_point(y, 3);
m_current_pos.y() = y;
return buf;
} }
std::string set_format_Z(float z) { std::string set_format_Z(float z) {
char buf[64]; return " Z" + Slic3r::float_to_string_decimal_point(z, 3);
sprintf(buf, " Z%.3f", z);
return buf;
} }
std::string set_format_E(float e) { std::string set_format_E(float e) {
char buf[64]; return " E" + Slic3r::float_to_string_decimal_point(e, 4);
sprintf(buf, " E%.4f", e);
return buf;
} }
std::string set_format_F(float f) { std::string set_format_F(float f) {
char buf[64]; char buf[64];
sprintf(buf, " F%d", int(floor(f + 0.5f))); sprintf(buf, " F%d", int(floor(f + 0.5f)));
m_current_feedrate = f; m_current_feedrate = f;
return buf; return buf;
} }
WipeTowerWriter& operator=(const WipeTowerWriter &rhs); 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. // 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(); Vec2f current_pos = writer.pos_rotated();
writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483 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()) .append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
+ " Y" + std::to_string(current_pos.y()) + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
+ never_skip_tag() + "\n"); + never_skip_tag() + "\n");
// The toolchange Tn command will be inserted later, only in case that the user does // 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); assert(first.new_tool == second.initial_tool);
WipeTower::ToolChangeResult out = first; WipeTower::ToolChangeResult out = first;
if (first.end_pos != second.start_pos) { if (first.end_pos != second.start_pos)
char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3)
sprintf(buf, "G1 X%.3f Y%.3f F7200\n", second.start_pos.x(), second.start_pos.y()); + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3)
out.gcode += buf; + " F7200\n";
}
out.gcode += second.gcode; out.gcode += second.gcode;
out.extrusions.insert(out.extrusions.end(), second.extrusions.begin(), second.extrusions.end()); out.extrusions.insert(out.extrusions.end(), second.extrusions.begin(), second.extrusions.end());
out.end_pos = second.end_pos; out.end_pos = second.end_pos;

View File

@ -6,6 +6,8 @@
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "LocalesUtils.hpp"
#include <Shiny/Shiny.h> #include <Shiny/Shiny.h>
namespace Slic3r { 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<const char*, const char*> &command) const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command)
{ {
PROFILE_FUNC(); PROFILE_FUNC();
CNumericLocalesSetter locales_setter; // for strtod
// command and args // command and args
const char *c = ptr; 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 bool GCodeReader::GCodeLine::has_value(char axis, float &value) const
{ {
CNumericLocalesSetter locales_setter; // for strtod
const char *c = m_raw.c_str(); const char *c = m_raw.c_str();
// Skip the whitespaces. // Skip the whitespaces.
c = skip_whitespaces(c); c = skip_whitespaces(c);

View File

@ -0,0 +1,76 @@
#include "LocalesUtils.hpp"
#include <stdexcept>
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.data());
#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] == '.';
}
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());
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

View File

@ -0,0 +1,47 @@
#ifndef slic3r_LocalesUtils_hpp_
#define slic3r_LocalesUtils_hpp_
#include <string>
#include <clocale>
#include <iomanip>
#include <cassert>
#ifdef __APPLE__
#include <xlocale.h>
#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);
double string_to_double_decimal_point(const std::string& str, size_t* pos = nullptr);
} // namespace Slic3r
#endif // slic3r_LocalesUtils_hpp_

View File

@ -11,6 +11,8 @@
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "LocalesUtils.hpp"
namespace Slic3r { namespace Slic3r {
class BoundingBox; class BoundingBox;
@ -88,10 +90,10 @@ inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale<d
inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); } inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); }
inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); } inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(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 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("[") + std::to_string(pt(0)) + ", " + std::to_string(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("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; } 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("[") + 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("[") + float_to_string_decimal_point(pt(0)) + ", " + float_to_string_decimal_point(pt(1)) + ", " + float_to_string_decimal_point(pt(2)) + "]"; }
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t); std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
Pointf3s transform(const Pointf3s& points, const Transform3d& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t);

View File

@ -166,8 +166,8 @@ struct FaceHash {
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
// Return a concatenated string representation of the coordinates // 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) FaceHash(const indexed_triangle_set &its)
{ {

View File

@ -266,7 +266,7 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_
std::string bitmap_key = bitmap_name + ( target_height !=0 ? std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) : "-h" + std::to_string(target_height) :
"-w" + std::to_string(target_width)) "-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" : ""); + (grayscale ? "-gs" : "");
/* For the Dark mode of any platform, we should draw icons in respect to OS background /* For the Dark mode of any platform, we should draw icons in respect to OS background

View File

@ -1388,17 +1388,21 @@ static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value)
{ {
e.Skip(); e.Skip();
wxString str = ctrl->GetValue(); 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; double val = 0.0;
if (!str.ToCDouble(&val)) { if (!str.ToDouble(&val)) {
if (val == 0.0) if (val == 0.0)
val = def_value; val = def_value;
ctrl->SetValue(double_to_string(val)); ctrl->SetValue(double_to_string(val));
show_error(nullptr, _L("Invalid numeric input.")); show_error(nullptr, _L("Invalid numeric input."));
ctrl->SetFocus(); ctrl->SetFocus();
} }
else if (was_replace) else if (was_replaced)
ctrl->SetValue(double_to_string(val)); ctrl->SetValue(double_to_string(val));
} }
@ -1447,17 +1451,17 @@ PageDiameters::PageDiameters(ConfigWizard *parent)
void PageDiameters::apply_custom_config(DynamicPrintConfig &config) void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
{ {
double val = 0.0; double val = 0.0;
diam_nozzle->GetValue().ToCDouble(&val); diam_nozzle->GetValue().ToDouble(&val);
auto *opt_nozzle = new ConfigOptionFloats(1, val); auto *opt_nozzle = new ConfigOptionFloats(1, val);
config.set_key_value("nozzle_diameter", opt_nozzle); config.set_key_value("nozzle_diameter", opt_nozzle);
val = 0.0; val = 0.0;
diam_filam->GetValue().ToCDouble(&val); diam_filam->GetValue().ToDouble(&val);
auto * opt_filam = new ConfigOptionFloats(1, val); auto * opt_filam = new ConfigOptionFloats(1, val);
config.set_key_value("filament_diameter", opt_filam); config.set_key_value("filament_diameter", opt_filam);
auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { 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); sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4);
config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false));
}; };

View File

@ -2162,7 +2162,7 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0,
bool disable = textctrl->IsEmpty(); bool disable = textctrl->IsEmpty();
if (!disable && min >= 0.0 && max >= 0.0) { if (!disable && min >= 0.0 && max >= 0.0) {
double value = -1.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; disable = true;
else else
disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ? 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; return -1.0;
double value = -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*/) void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/)

View File

@ -98,9 +98,14 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
return; return;
} }
str.Replace(",", ".", false); char dec_sep = '.';
if (! is_decimal_separator_point()) {
str.Replace(".", ",", false);
dec_sep = ',';
}
double val; 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 val = 3.0; // default value
if (fabs(m_sequence.interval_by_layers - val) < 0.001) if (fabs(m_sequence.interval_by_layers - val) < 0.001)

View File

@ -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. // 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 number is in scientific format, trailing zeroes belong to the exponent and cannot be removed.
if (s.find_first_of("eE") == wxString::npos) { 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. // No decimal point => removing trailing zeroes irrelevant for integer number.
if (posDecSep != wxString::npos) { if (posDecSep != wxString::npos) {
// Find the last character to keep. // Find the last character to keep.
size_t posLastNonZero = s.find_last_not_of("0"); 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) if (posLastNonZero == posDecSep)
-- posLastNonZero; -- posLastNonZero;
s.erase(posLastNonZero + 1); 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); m_value = double(m_opt.min);
break; break;
} }
double val; double val;
// Replace the first occurence of comma in decimal number.
str.Replace(",", ".", false); const char dec_sep = is_decimal_separator_point() ? '.' : ',';
if (str == ".") 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; val = 0.0;
else else
{ {
if (m_opt.nullable && str == na_value()) if (m_opt.nullable && str == na_value())
val = ConfigOptionFloatsNullable::nil_value(); val = ConfigOptionFloatsNullable::nil_value();
else if (!str.ToCDouble(&val)) else if (!str.ToDouble(&val))
{ {
if (!check_value) { if (!check_value) {
m_value.clear(); 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() != '%') if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%')
{ {
double val = 0.; double val = 0.;
// Replace the first occurence of comma in decimal number. const char dec_sep = is_decimal_separator_point() ? '.' : ',';
str.Replace(",", ".", false); 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 // remove space and "mm" substring, if any exists
str.Replace(" ", "", true); str.Replace(" ", "", true);
str.Replace("m", "", true); str.Replace("m", "", true);
if (!str.ToCDouble(&val)) if (!str.ToDouble(&val))
{ {
if (!check_value) { if (!check_value) {
m_value.clear(); m_value.clear();
@ -1215,8 +1226,8 @@ boost::any& Choice::get_value()
return m_value; return m_value;
} }
void Choice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); }; void Choice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); }
void Choice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }; void Choice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }
void Choice::msw_rescale() void Choice::msw_rescale()
{ {
@ -1478,7 +1489,7 @@ boost::any& PointCtrl::get_value()
!y_textctrl->GetValue().ToDouble(&y)) !y_textctrl->GetValue().ToDouble(&y))
{ {
set_value(m_value.empty() ? Vec2d(0.0, 0.0) : m_value, true); 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 else
if (m_opt.min > x || x > m_opt.max || if (m_opt.min > x || x > m_opt.max ||

View File

@ -5,6 +5,7 @@
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "Plater.hpp" #include "Plater.hpp"
@ -78,6 +79,7 @@ static float round_to_nearest(float value, unsigned int decimals)
res = std::round(value); res = std::round(value);
else { else {
char buf[64]; char buf[64];
// locales should not matter, both sprintf and stof are sensitive, so...
sprintf(buf, "%.*g", decimals, value); sprintf(buf, "%.*g", decimals, value);
res = std::stof(buf); res = std::stof(buf);
} }
@ -990,6 +992,9 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
// save materials file // save materials file
boost::filesystem::path mat_filename(filename); boost::filesystem::path mat_filename(filename);
mat_filename.replace_extension("mtl"); mat_filename.replace_extension("mtl");
CNumericLocalesSetter locales_setter;
FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
if (fp == nullptr) { if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
@ -3465,18 +3470,16 @@ void GCodeViewer::render_statistics() const
ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWrapper& imgui = *wxGetApp().imgui();
auto add_time = [this, &imgui](const std::string& label, int64_t time) { 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<float>(time) * 0.001f).c_str());
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
ImGui::SameLine(offset); ImGui::SameLine(offset);
imgui.text(buf); imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast<float>(time) * 0.001f) + ")");
}; };
auto add_memory = [this, &imgui](const std::string& label, int64_t memory) { auto add_memory = [this, &imgui](const std::string& label, int64_t memory) {
auto format_string = [memory](const std::string& units, float value) { auto format_string = [memory](const std::string& units, float value) {
char buf[1024]; return std::to_string(memory) + " bytes (" +
sprintf(buf, "%lld bytes (%.3f %s)", memory, static_cast<float>(memory) * value, units.c_str()); Slic3r::float_to_string_decimal_point(float(memory) * value, 3)
return std::string(buf); + " " + units + ")";
}; };
static const float kb = 1024.0f; static const float kb = 1024.0f;
@ -3496,11 +3499,9 @@ void GCodeViewer::render_statistics() const
}; };
auto add_counter = [this, &imgui](const std::string& label, int64_t counter) { 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.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
ImGui::SameLine(offset); 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); 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);

View File

@ -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) { 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.distance = std::max(dist_min, settings.distance);
settings_out.distance = 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; settings_changed = true;
} }
@ -3815,7 +3815,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x)
if (imgui->button(_L("Reset"))) { if (imgui->button(_L("Reset"))) {
settings_out = ArrangeSettings{}; settings_out = ArrangeSettings{};
settings_out.distance = std::max(dist_min, settings_out.distance); 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"); appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0");
settings_changed = true; settings_changed = true;
} }

View File

@ -2,6 +2,8 @@
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include <string> #include <string>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -113,7 +115,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
str.pop_back(); str.pop_back();
percent = true; percent = true;
} }
double val = stod(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)); config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
break;} break;}
case coPercent: case coPercent:

View File

@ -1586,7 +1586,7 @@ bool GUI_App::load_language(wxString language, bool initial)
m_wxLocale->AddCatalog(SLIC3R_APP_KEY); m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
m_imgui->set_language(into_u8(language_info->CanonicalName)); 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. //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()); Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data());
return true; return true;
} }

View File

@ -397,12 +397,16 @@ coordf_t LayerRangeEditor::get_value()
wxString str = GetValue(); wxString str = GetValue();
coordf_t layer_height; coordf_t layer_height;
// Replace the first occurence of comma in decimal number. const char dec_sep = is_decimal_separator_point() ? '.' : ',';
str.Replace(",", ".", false); 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 == ".") if (str == ".")
layer_height = 0.0; layer_height = 0.0;
else { 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.")); show_error(m_parent, _L("Invalid numeric input."));
SetValue(double_to_string(layer_height)); SetValue(double_to_string(layer_height));
} }

View File

@ -1095,15 +1095,19 @@ double ManipulationEditor::get_value()
wxString str = GetValue(); wxString str = GetValue();
double value; double value;
// Replace the first occurence of comma in decimal number. const char dec_sep = is_decimal_separator_point() ? '.' : ',';
str.Replace(",", ".", false); 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 == ".") if (str == ".")
value = 0.0; 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; str = m_valid_value;
SetValue(str); SetValue(str);
str.ToCDouble(&value); str.ToDouble(&value);
} }
return value; return value;

View File

@ -11,6 +11,7 @@
#include "Camera.hpp" #include "Camera.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#include "libslic3r/PresetBundle.hpp" #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) if (pos == std::string::npos)
return; 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 // extract min_z
field = field.substr(0, pos); 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) if (pos == std::string::npos)
return; 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 // extract type
field = field.substr(0, pos); field = field.substr(0, pos);

View File

@ -1,6 +1,7 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/LocalesUtils.hpp"
using namespace Slic3r; 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") { WHEN("A string option is set through the double interface") {
config.set("end_gcode", 100.5); config.set("end_gcode", 100.5);
THEN("The underlying value is set correctly.") { THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("end_gcode")->value == std::to_string(100.5)); REQUIRE(config.opt<ConfigOptionString>("end_gcode")->value == float_to_string_decimal_point(100.5));
} }
} }
WHEN("A float or percent is set as a percent through the string interface.") { WHEN("A float or percent is set as a percent through the string interface.") {