From 0d0c09288058ee0b3e5565f3087321cd530038d6 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 10 Sep 2021 10:46:58 +0200 Subject: [PATCH 01/23] Object warning notification Opens when loading object, closes at deletion. Replaces SimplifySuggesion. --- src/slic3r/GUI/NotificationManager.cpp | 17 +++++++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 13 ++++++++++--- src/slic3r/GUI/Plater.cpp | 17 +++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 4e94d82608..172e38d53a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1250,6 +1250,14 @@ void NotificationManager::close_slicing_error_notification(const std::string& te } } } +void NotificationManager::push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) +{ + NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotification, 0, text, hypertext, callback }; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + notification->object_id = object_id; + notification->warning_step = 0; + push_notification_data(std::move(notification), 0); +} void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) { std::string hypertext; @@ -1304,6 +1312,15 @@ void NotificationManager::remove_slicing_warnings_of_released_objects(const std: notification->close(); } } +void NotificationManager::remove_object_warnings_of_released_objects(const std::vector& living_oids) +{ + for (std::unique_ptr& notification : m_pop_notifications) + if (notification->get_type() == NotificationType::ObjectWarning) { + if (!std::binary_search(living_oids.begin(), living_oids.end(), + static_cast(notification.get())->object_id)) + notification->close(); + } +} void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) { close_notification_of_type(NotificationType::ExportFinished); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 1e667e4525..eb95aec580 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -71,6 +71,9 @@ enum class NotificationType PlaterError, // Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled. PlaterWarning, + // Warning connected to single object id, appears at loading object, disapears at deletition. + // Example: advice to simplify object with big amount of triangles. + ObjectWarning, // Progress bar instead of text. ProgressBar, // Progress bar with info from Print Host Upload Queue dialog. @@ -97,8 +100,6 @@ enum class NotificationType // Shows when ObjectList::update_info_items finds information that should be stressed to the user // Might contain logo taken from gizmos UpdatedItemsInfo, - // Give user advice to simplify object with big amount of triangles - SimplifySuggestion }; class NotificationManager @@ -155,6 +156,12 @@ public: // Closes error or warning of the same text void close_plater_error_notification(const std::string& text); void close_plater_warning_notification(const std::string& text); + // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. + void push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", + std::function callback = std::function()); + // Close object warnings, whose ObjectID is not in the list. + // living_oids is expected to be sorted. + void remove_object_warnings_of_released_objects(const std::vector& living_oids); // Creates special notification slicing complete. // If large = true (Plater side bar is closed), then printing time and export button is shown // at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s. @@ -576,7 +583,7 @@ private: NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, - NotificationType::SimplifySuggestion + NotificationType::ObjectWarning }; //prepared (basic) notifications static const NotificationData basic_notifications[]; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 656b879722..ac76fe6559 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1736,6 +1736,7 @@ struct Plater::priv void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); // Update notification manager with the current state of warnings produced by the background process (slicing). void actualize_slicing_warnings(const PrintBase &print); + void actualize_object_warnings(const PrintBase& print); // Displays dialog window with list of warnings. // Returns true if user clicks OK. // Returns true if current_warnings vector is empty without showning the dialog @@ -3048,6 +3049,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool //actualizate warnings if (invalidated != Print::APPLY_STATUS_UNCHANGED) { actualize_slicing_warnings(*this->background_process.current_print()); + actualize_object_warnings(*this->background_process.current_print()); show_warning_dialog = false; process_completed_with_error = false; } @@ -3587,10 +3589,7 @@ void Plater::priv::create_simplify_notification(const std::vector& obj_i manager.open_gizmo(GLGizmosManager::EType::Simplify); return true; }; - notification_manager->push_notification( - NotificationType::SimplifySuggestion, - NotificationManager::NotificationLevel::WarningNotification, - text.str(), hypertext, open_simplify); + notification_manager->push_object_warning_notification(text.str(), model.objects[object_id]->id(), hypertext, open_simplify); } } @@ -3855,6 +3854,16 @@ void Plater::priv::actualize_slicing_warnings(const PrintBase &print) notification_manager->remove_slicing_warnings_of_released_objects(ids); notification_manager->set_all_slicing_warnings_gray(true); } +void Plater::priv::actualize_object_warnings(const PrintBase& print) +{ + std::vector ids; + for (const ModelObject* object : print.model().objects ) + { + ids.push_back(object->id()); + } + std::sort(ids.begin(), ids.end()); + notification_manager->remove_object_warnings_of_released_objects(ids); +} void Plater::priv::clear_warnings() { notification_manager->close_slicing_errors_and_warnings(); From 03b60486840f32e32dc54103dc3051f94e79b35a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Sep 2021 11:43:53 +0200 Subject: [PATCH 02/23] Follow-up to beee18f22991e369b1722a43bbcb692fa0d68af0 WIP to G-code export parallelization through pipelining: Decoupled CoolingBuffer from GCode / GCodeWriter, ready to be pipelined on a different thread. --- src/libslic3r/GCode.cpp | 6 +-- src/libslic3r/GCode/CoolingBuffer.cpp | 65 +++++++++++------------ src/libslic3r/GCode/CoolingBuffer.hpp | 23 ++++++--- src/libslic3r/GCodeWriter.cpp | 74 ++++++++++++++------------- src/libslic3r/GCodeWriter.hpp | 10 ++-- t/cooling.t | 2 +- xs/xsp/GCode.xsp | 1 - 7 files changed, 96 insertions(+), 85 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index efea240e55..7e1afb5a8a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1219,7 +1219,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Disable fan. if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id)) - file.write(m_writer.set_fan(0, true)); + file.write(m_writer.set_fan(0)); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); @@ -1334,7 +1334,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.writeln(between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). - m_cooling_buffer->reset(); + m_cooling_buffer->reset(this->writer().get_position()); m_cooling_buffer->set_current_extruder(initial_extruder_id); // Pair the object layers with the support layers by z, extrude them. std::vector layers_to_print = collect_layers_to_print(object); @@ -1420,7 +1420,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write end commands to file. file.write(this->retract()); - file.write(m_writer.set_fan(false)); + file.write(m_writer.set_fan(0)); // adds tag for processor file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 17f3e0b737..9ca85c7281 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -16,19 +16,25 @@ namespace Slic3r { -CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0) +CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) { - this->reset(); + this->reset(gcodegen.writer().get_position()); + + const std::vector &extruders = gcodegen.writer().extruders(); + m_extruder_ids.reserve(extruders.size()); + for (const Extruder &ex : extruders) { + m_num_extruders = std::max(ex.id() + 1, m_num_extruders); + m_extruder_ids.emplace_back(ex.id()); + } } -void CoolingBuffer::reset() +void CoolingBuffer::reset(const Vec3d &position) { m_current_pos.assign(5, 0.f); - Vec3d pos = m_gcodegen.writer().get_position(); - m_current_pos[0] = float(pos(0)); - m_current_pos[1] = float(pos(1)); - m_current_pos[2] = float(pos(2)); - m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); + m_current_pos[0] = float(position.x()); + m_current_pos[1] = float(position.y()); + m_current_pos[2] = float(position.z()); + m_current_pos[4] = float(m_config.travel_speed.value); } struct CoolingLine @@ -303,30 +309,23 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b // Return the list of parsed lines, bucketed by an extruder. std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const { - const FullPrintConfig &config = m_gcodegen.config(); - const std::vector &extruders = m_gcodegen.writer().extruders(); - unsigned int num_extruders = 0; - for (const Extruder &ex : extruders) - num_extruders = std::max(ex.id() + 1, num_extruders); - - std::vector per_extruder_adjustments(extruders.size()); - std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); - for (size_t i = 0; i < extruders.size(); ++ i) { + std::vector per_extruder_adjustments(m_extruder_ids.size()); + std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); + for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { PerExtruderAdjustments &adj = per_extruder_adjustments[i]; - unsigned int extruder_id = extruders[i].id(); + unsigned int extruder_id = m_extruder_ids[i]; adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); - adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id)); - adj.min_print_speed = float(config.min_print_speed.get_at(extruder_id)); + adj.cooling_slow_down_enabled = m_config.cooling.get_at(extruder_id); + adj.slowdown_below_layer_time = float(m_config.slowdown_below_layer_time.get_at(extruder_id)); + adj.min_print_speed = float(m_config.min_print_speed.get_at(extruder_id)); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); unsigned int current_extruder = m_current_extruder; PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; const char *line_start = gcode.c_str(); const char *line_end = line_start; - const char extrusion_axis = get_extrusion_axis(config)[0]; + const char extrusion_axis = get_extrusion_axis(m_config)[0]; // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); @@ -387,7 +386,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } if ((line.type & CoolingLine::TYPE_G92) == 0) { // G0 or G1. Calculate the duration. - if (config.use_relative_e_distances.value) + if (m_config.use_relative_e_distances.value) // Reset extruder accumulator. current_pos[3] = 0.f; float dif[4]; @@ -430,8 +429,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { line.type = CoolingLine::TYPE_EXTRUDE_END; active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); + } else if (boost::starts_with(sline, m_toolchange_prefix)) { + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { @@ -641,7 +640,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector slowdown_below_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. } else { - // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. + // Adjust this and all the following (higher m_config.slowdown_below_layer_time) extruders. // Sum maximum slow down time as if everything was slowed down including the external perimeters. float max_time = elapsed_time_total0; for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) @@ -694,8 +693,7 @@ std::string CoolingBuffer::apply_layer_cooldown( bool bridge_fan_control = false; int bridge_fan_speed = 0; auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { - const FullPrintConfig &config = m_gcodegen.config(); -#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); @@ -737,13 +735,12 @@ std::string CoolingBuffer::apply_layer_cooldown( } if (fan_speed_new != fan_speed) { fan_speed = fan_speed_new; - new_gcode += m_gcodegen.writer().set_fan(fan_speed); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); } }; const char *pos = gcode.c_str(); int current_feedrate = 0; - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); change_extruder_set_fan(); for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; @@ -751,7 +748,7 @@ std::string CoolingBuffer::apply_layer_cooldown( if (line_start > pos) new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size()); + unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); @@ -759,10 +756,10 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.append(line_start, line_end - line_start); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) - new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) - new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 0932d62d37..5f49ef4557 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -23,10 +23,9 @@ struct PerExtruderAdjustments; class CoolingBuffer { public: CoolingBuffer(GCode &gcodegen); - void reset(); + void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); - GCode* gcodegen() { return &m_gcodegen; } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; @@ -36,17 +35,25 @@ private: // Returns the adjusted G-code. std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); - GCode& m_gcodegen; // G-code snippet cached for the support layers preceding an object layer. - std::string m_gcode; + std::string m_gcode; // Internal data. // X,Y,Z,E,F - std::vector m_axis; - std::vector m_current_pos; - unsigned int m_current_extruder; + std::vector m_axis; + std::vector m_current_pos; + // Cached from GCodeWriter. + // Printing extruder IDs, zero based. + std::vector m_extruder_ids; + // Highest of m_extruder_ids plus 1. + unsigned int m_num_extruders { 0 }; + const std::string m_toolchange_prefix; + // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, + // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. + const PrintConfig &m_config; + unsigned int m_current_extruder; // Old logic: proportional. - bool m_cooling_logic_proportional = false; + bool m_cooling_logic_proportional = false; }; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index c97180982d..793a666752 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -156,41 +156,6 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait return gcode.str(); } -std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) -{ - std::ostringstream gcode; - if (m_last_fan_speed != speed || dont_save) { - if (!dont_save) m_last_fan_speed = speed; - - if (speed == 0) { - if (FLAVOR_IS(gcfTeacup)) { - gcode << "M106 S0"; - } else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { - gcode << "M127"; - } else { - gcode << "M107"; - } - if (this->config.gcode_comments) gcode << " ; disable fan"; - gcode << "\n"; - } else { - if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { - gcode << "M126"; - } else { - gcode << "M106 "; - if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { - gcode << "P"; - } else { - gcode << "S"; - } - gcode << (255.0 * speed / 100.0); - } - if (this->config.gcode_comments) gcode << " ; enable fan"; - gcode << "\n"; - } - } - return gcode.str(); -} - std::string GCodeWriter::set_acceleration(unsigned int acceleration) { // Clamp the acceleration to the allowed maximum. @@ -611,4 +576,43 @@ std::string GCodeWriter::unlift() return gcode; } +std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed) +{ + std::ostringstream gcode; + if (speed == 0) { + switch (gcode_flavor) { + case gcfTeacup: + gcode << "M106 S0"; break; + case gcfMakerWare: + case gcfSailfish: + gcode << "M127"; break; + default: + gcode << "M107"; break; + } + if (gcode_comments) + gcode << " ; disable fan"; + gcode << "\n"; + } else { + switch (gcode_flavor) { + case gcfMakerWare: + case gcfSailfish: + gcode << "M126"; break; + case gcfMach3: + case gcfMachinekit: + gcode << "M106 P" << 255.0 * speed / 100.0; break; + default: + gcode << "M106 S" << 255.0 * speed / 100.0; break; + } + if (gcode_comments) + gcode << " ; enable fan"; + gcode << "\n"; + } + return gcode.str(); +} + +std::string GCodeWriter::set_fan(unsigned int speed) const +{ + return GCodeWriter::set_fan(this->config.gcode_flavor, this->config.gcode_comments, speed); +} + } diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 2de95ecc5b..dd602ca804 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -18,7 +18,7 @@ public: GCodeWriter() : multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), m_single_extruder_multi_material(false), - m_last_acceleration(0), m_max_acceleration(0), m_last_fan_speed(0), + m_last_acceleration(0), m_max_acceleration(0), m_last_bed_temperature(0), m_last_bed_temperature_reached(true), m_lifted(0) {} @@ -42,7 +42,6 @@ public: std::string postamble() const; std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; std::string set_bed_temperature(unsigned int temperature, bool wait = false); - std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_acceleration(unsigned int acceleration); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; @@ -69,6 +68,12 @@ public: std::string unlift(); Vec3d get_position() const { return m_pos; } + // To be called by the CoolingBuffer from another thread. + static std::string set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed); + // To be called by the main thread. It always emits the G-code, it does not remember the previous state. + // Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread. + std::string set_fan(unsigned int speed) const; + private: // Extruders are sorted by their ID, so that binary search is possible. std::vector m_extruders; @@ -79,7 +84,6 @@ private: // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. // If set to zero, the limit is not in action. unsigned int m_max_acceleration; - unsigned int m_last_fan_speed; unsigned int m_last_bed_temperature; bool m_last_bed_temperature_reached; double m_lifted; diff --git a/t/cooling.t b/t/cooling.t index 2f444cf9d1..a7720fd07c 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -132,7 +132,7 @@ $config->set('disable_fan_first_layers', [ 0 ]); 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] }); - $buffer->gcodegen->set_extruders([ 0, 1 ]); + $gcodegen->set_extruders([ 0, 1 ]); my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 78a5dde376..4c25838946 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -10,7 +10,6 @@ CoolingBuffer(GCode* gcode) %code{% RETVAL = new CoolingBuffer(*gcode); %}; ~CoolingBuffer(); - Ref gcodegen(); std::string process_layer(std::string gcode, size_t layer_id) %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %}; From dc4b783e9ec38756432789e97b6089d62b6b1d14 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Sep 2021 12:10:00 +0200 Subject: [PATCH 03/23] Pimping up SpiralVase code, fix of 03b60486840f32e32dc54103dc3051f94e79b35a --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/SpiralVase.cpp | 2 +- src/libslic3r/GCode/SpiralVase.hpp | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7e1afb5a8a..2347225ab7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1099,7 +1099,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); print.throw_if_canceled(); - m_cooling_buffer = make_unique(*this); if (print.config().spiral_vase.value) m_spiral_vase = make_unique(print.config()); #ifdef HAS_PRESSURE_EQUALIZER @@ -1212,6 +1211,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } print.throw_if_canceled(); + m_cooling_buffer = make_unique(*this); m_cooling_buffer->set_current_extruder(initial_extruder_id); // Emit machine envelope limits for the Marlin firmware. diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index acb6ad0348..c3caee2dc4 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -54,7 +54,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) // For absolute extruder distances it will be switched off. // Tapering the absolute extruder distances requires to process every extrusion value after the first transition // layer. - bool transition = m_transition_layer && m_config->use_relative_e_distances.value; + bool transition = m_transition_layer && m_config.use_relative_e_distances.value; float layer_height_factor = layer_height / total_layer_length; float len = 0.f; m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index 5353901fe6..fb461c2015 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -8,10 +8,10 @@ namespace Slic3r { class SpiralVase { public: - SpiralVase(const PrintConfig &config) : m_config(&config) + SpiralVase(const PrintConfig &config) : m_config(config) { - m_reader.z() = (float)m_config->z_offset; - m_reader.apply_config(*m_config); + m_reader.z() = (float)m_config.z_offset; + m_reader.apply_config(m_config); }; void enable(bool en) { @@ -22,7 +22,7 @@ public: std::string process_layer(const std::string &gcode); private: - const PrintConfig *m_config; + const PrintConfig &m_config; GCodeReader m_reader; bool m_enabled = false; @@ -32,4 +32,4 @@ private: } -#endif +#endif // slic3r_SpiralVase_hpp_ From 34c4b74af49c12e89fc6b5cc2bcfa36c69797578 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Sep 2021 12:28:52 +0200 Subject: [PATCH 04/23] Fixed Perl unit tests --- t/cooling.t | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/t/cooling.t b/t/cooling.t index a7720fd07c..9ac5a24f4d 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -33,7 +33,10 @@ sub buffer { $gcodegen = Slic3r::GCode->new; $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); - $gcodegen->set_extruders([ 0 ]); + + my @extruders = shift; + @extruders = [ 0 ] if int(@extruders) == 0; + $gcodegen->set_extruders(\@extruders); return Slic3r::GCode::CoolingBuffer->new($gcodegen); } @@ -131,8 +134,8 @@ $config->set('disable_fan_first_layers', [ 0 ]); 'cooling' => [ 1 , 0 ], 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] - }); - $gcodegen->set_extruders([ 0, 1 ]); + }, + [ 0, 1]); my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; From 3db4804e476aa4b6cb320f696bc6ddd094c9087a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Sep 2021 14:07:29 +0200 Subject: [PATCH 05/23] MSW specific: ObjectList: Fixed eternal Editor Control Steps to reproduce of a bug: 1. Add object 2. Click the Cog icon with the left mouse button 3. DoubleClick on name of object > An Editor Control appears that cannot be deleted any way --- src/slic3r/GUI/ExtraRenderers.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index e9fb7339f3..5fe86db275 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -197,6 +197,17 @@ wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRec labelRect.SetWidth(labelRect.GetWidth() - bmp_width); } +#ifdef __WXMSW__ + // Case when from some reason we try to create next EditorCtrl till old one was not deleted + if (auto children = parent->GetChildren(); children.GetCount() > 0) + for (auto child : children) + if (dynamic_cast(child)) { + parent->RemoveChild(child); + child->Destroy(); + break; + } +#endif // __WXMSW__ + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), position, labelRect.GetSize(), wxTE_PROCESS_ENTER); text_editor->SetInsertionPointEnd(); From b9dab7540e5de0d81a74cb53c2b00fb4b5857a38 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 10 Sep 2021 14:31:31 +0200 Subject: [PATCH 06/23] Removes bottom status bar. StatusBar class calls are commented out and replaced with notifications. SlicicingProgress notification shows progress of slicing, ProgressIndicator notification handles other progress information, like arrange objects etc. --- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 4 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +- src/slic3r/GUI/HintNotification.cpp | 2 +- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 5 +- src/slic3r/GUI/Jobs/FillBedJob.hpp | 5 +- src/slic3r/GUI/Jobs/Job.cpp | 24 +- src/slic3r/GUI/Jobs/Job.hpp | 7 +- src/slic3r/GUI/Jobs/PlaterJob.hpp | 5 +- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 6 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 5 +- src/slic3r/GUI/Jobs/SLAImportJob.hpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 16 +- src/slic3r/GUI/MainFrame.hpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 739 ++++++++++++++---- src/slic3r/GUI/NotificationManager.hpp | 228 ++++-- src/slic3r/GUI/Plater.cpp | 126 +-- src/slic3r/GUI/Plater.hpp | 6 +- src/slic3r/GUI/ProgressStatusBar.cpp | 1 + src/slic3r/GUI/ProgressStatusBar.hpp | 1 + 23 files changed, 908 insertions(+), 292 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3d5e56b462..79186a8ea2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -958,6 +958,8 @@ bool GUI_App::on_init_inner() // update_mode(); // !!! do that later SetTopWindow(mainframe); + plater_->init_notification_manager(); + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); if (is_gcode_viewer()) { @@ -2278,7 +2280,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const return mainframe->m_tabpanel; } -NotificationManager* GUI_App::notification_manager() +std::shared_ptr GUI_App::notification_manager() { return plater_->get_notification_manager(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a088e10d4c..5acab0c948 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -276,7 +276,7 @@ public: ObjectLayers* obj_layers(); Plater* plater(); Model& model(); - NotificationManager* notification_manager(); + std::shared_ptr notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. GUI_InitParams* init_params { nullptr }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 45da928f57..eb511177df 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -734,9 +734,9 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); if( bottom_area - top_area > delta_area) { - NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); + std::shared_ptr notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( - NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, + NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("Apply auto color change to print"), [this](wxEvtHandler*) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 608e7bd1fa..4bd98f8c87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -22,7 +22,7 @@ static inline void show_notification_extruders_limit_exceeded() wxGetApp() .plater() ->get_notification_manager() - ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification, + ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotificationLevel, GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 9f49f9734a..a2c4910e34 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -337,7 +337,7 @@ void GLGizmoSimplify::on_set_state() auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->push_notification( NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("ERROR: Wait until Simplification ends or Cancel process.")); return; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 93eba3042c..7a9373a5f4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -194,7 +194,7 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const if (get_current_type() != type && get_current_type() != Undefined) { wxGetApp().plater()->get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("ERROR: Please close all manipulators available from " "the left toolbar first")); return false; @@ -1256,7 +1256,7 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const if (error_notification) wxGetApp().plater()->get_notification_manager()->push_notification( NotificationType::QuitSLAManualMode, - NotificationManager::NotificationLevel::ErrorNotification, + NotificationManager::NotificationLevel::ErrorNotificationLevel, _u8L("You are currently editing SLA support points. Please, " "apply or discard your changes first.")); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index fe5eeb67c2..53435e318c 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -1012,7 +1012,7 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true if(hint_data != nullptr) { NotificationData nd { NotificationType::DidYouKnowHint, - NotificationLevel::RegularNotification, + NotificationLevel::RegularNotificationLevel, 0, hint_data->text, hint_data->hypertext, nullptr, diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 2744920da8..2ccb7a04f1 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class ModelInstance; namespace GUI { +class NotificationManager; class ArrangeJob : public PlaterJob { @@ -39,8 +40,8 @@ protected: void process() override; public: - ArrangeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + ArrangeJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} int status_range() const override diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index bf407656d1..548974fa6b 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -6,6 +6,7 @@ namespace Slic3r { namespace GUI { class Plater; +class NotificationManager; class FillBedJob : public PlaterJob { @@ -27,8 +28,8 @@ protected: void process() override; public: - FillBedJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + FillBedJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} int status_range() const override diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index 6346227aab..c198de40ca 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -2,9 +2,11 @@ #include #include "Job.hpp" +#include "../NotificationManager.hpp" #include #include + namespace Slic3r { void GUI::Job::run(std::exception_ptr &eptr) @@ -30,8 +32,8 @@ void GUI::Job::update_status(int st, const wxString &msg) wxQueueEvent(this, evt); } -GUI::Job::Job(std::shared_ptr pri) - : m_progress(std::move(pri)) +GUI::Job::Job(std::shared_ptr nm) + : m_notifications(nm) { m_thread_evt_id = wxNewId(); @@ -40,21 +42,21 @@ GUI::Job::Job(std::shared_ptr pri) auto msg = evt.GetString(); if (!msg.empty() && !m_worker_error) - m_progress->set_status_text(msg.ToUTF8().data()); + m_notifications->progress_indicator_set_status_text(msg.ToUTF8().data()); if (m_finalized) return; - m_progress->set_progress(evt.GetInt()); + m_notifications->progress_indicator_set_progress(evt.GetInt()); if (evt.GetInt() == status_range() || m_worker_error) { // set back the original range and cancel callback - m_progress->set_range(m_range); - m_progress->set_cancel_callback(); + m_notifications->progress_indicator_set_range(m_range); + m_notifications->progress_indicator_set_cancel_callback(); wxEndBusyCursor(); if (m_worker_error) { m_finalized = true; - m_progress->set_status_text(""); - m_progress->set_progress(m_range); + m_notifications->progress_indicator_set_status_text(""); + m_notifications->progress_indicator_set_progress(m_range); on_exception(m_worker_error); } else { @@ -86,12 +88,12 @@ void GUI::Job::start() prepare(); // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); + m_range = m_notifications->progress_indicator_get_range(); + m_notifications->progress_indicator_set_range(status_range()); // init cancellation flag and set the cancel callback m_canceled.store(false); - m_progress->set_cancel_callback( + m_notifications->progress_indicator_set_cancel_callback( [this]() { m_canceled.store(true); }); m_finalized = false; diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index 8243ce9430..ed80d8b5fc 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -8,14 +8,13 @@ #include -#include "ProgressIndicator.hpp" - #include #include namespace Slic3r { namespace GUI { +class NotificationManager; // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are @@ -33,7 +32,7 @@ class Job : public wxEvtHandler boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_progress; + std::shared_ptr m_notifications; std::exception_ptr m_worker_error = nullptr; void run(std::exception_ptr &); @@ -65,7 +64,7 @@ protected: } public: - Job(std::shared_ptr pri); + Job(std::shared_ptr nm); bool is_finalized() const { return m_finalized; } diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp index fcf0a54b80..52e5741aa4 100644 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ b/src/slic3r/GUI/Jobs/PlaterJob.hpp @@ -6,6 +6,7 @@ namespace Slic3r { namespace GUI { class Plater; +class NotificationManager; class PlaterJob : public Job { protected: @@ -15,8 +16,8 @@ protected: public: - PlaterJob(std::shared_ptr pri, Plater *plater): - Job{std::move(pri)}, m_plater{plater} {} + PlaterJob(std::shared_ptr nm, Plater *plater): + Job{nm}, m_plater{plater} {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cdb367f23a..f967bb7a37 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -10,6 +10,8 @@ namespace Slic3r { namespace GUI { +class NotificationManager; + class RotoptimizeJob : public PlaterJob { using FindFn = std::function pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + RotoptimizeJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} void finalize() override; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 027bf68492..9b9151f773 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -124,8 +125,8 @@ public: priv(Plater *plt) : plater{plt} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} +SLAImportJob::SLAImportJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater}, p{std::make_unique(plater)} {} SLAImportJob::~SLAImportJob() = default; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index 583c986559..7bc4d5e7e3 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -5,6 +5,8 @@ namespace Slic3r { namespace GUI { +class NotificationManager; + class SLAImportJob : public PlaterJob { class priv; @@ -16,7 +18,7 @@ protected: void finalize() override; public: - SLAImportJob(std::shared_ptr pri, Plater *plater); + SLAImportJob(std::shared_ptr nm, Plater *plater); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 51dbc201d3..6d5fbf11c7 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -155,13 +155,13 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetIcon(main_frame_icon(wxGetApp().get_app_mode())); // initialize status bar - m_statusbar = std::make_shared(this); - m_statusbar->set_font(GUI::wxGetApp().normal_font()); - if (wxGetApp().is_editor()) - m_statusbar->embed(this); - m_statusbar->set_status_text(_L("Version") + " " + - SLIC3R_VERSION + " - " + - _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); +// m_statusbar = std::make_shared(this); +// m_statusbar->set_font(GUI::wxGetApp().normal_font()); +// if (wxGetApp().is_editor()) +// m_statusbar->embed(this); +// m_statusbar->set_status_text(_L("Version") + " " + +// SLIC3R_VERSION + " - " + +// _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); // initialize tabpanel and menubar init_tabpanel(); @@ -1033,7 +1033,7 @@ void MainFrame::on_sys_color_changed() wxGetApp().init_label_colours(); #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); - m_statusbar->update_dark_ui(); + // m_statusbar->update_dark_ui(); #ifdef _MSW_DARK_MODE // update common mode sizer if (!wxGetApp().tabs_as_menu()) diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index e87f94f650..487d002af2 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -204,7 +204,7 @@ public: wxWindow* m_plater_page{ nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; - std::shared_ptr m_statusbar; +// std::shared_ptr m_statusbar; #ifdef __APPLE__ std::unique_ptr m_taskbar_icon; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 172e38d53a..0841efcb06 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -33,32 +33,32 @@ wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClic wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); const NotificationManager::NotificationData NotificationManager::basic_notifications[] = { - {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") }, + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; } }, - {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, - {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, + {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotificationLevel, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, - {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, + {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotificationLevel, 10, _u8L("No color change event was added to the print. The print does not look like a sign.") }, - {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, _u8L("Desktop integration was successful.") }, - {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Desktop integration failed.") }, - {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, _u8L("Undo desktop integration was successful.") }, - {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Undo desktop integration failed.") }, - //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, - //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, - //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification + //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification }; namespace { @@ -255,13 +255,13 @@ bool NotificationManager::PopNotification::push_background_color() push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } - if (m_data.level == NotificationLevel::ErrorNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } - if (m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::WarningNotificationLevel) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; @@ -276,9 +276,9 @@ void NotificationManager::PopNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -505,9 +505,9 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) { - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); imgui.text(text.c_str()); @@ -583,19 +583,23 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 int64_t now = GLCanvas3D::timestamp_now(); + // reset fade opacity for non-closing notifications or hover during fading + if (m_state != EState::FadingOut && m_state != EState::ClosePending && m_state != EState::Finished) { + m_current_fade_opacity = 1.0f; + } + // reset timers - hovered state is set in render if (m_state == EState::Hovered) { - m_current_fade_opacity = 1.0f; m_state = EState::Unknown; init(); // Timers when not fading - } else if (m_state != EState::NotFading && m_state != EState::FadingOut && m_data.duration != 0 && !paused) { + } else if (m_state != EState::NotFading && m_state != EState::FadingOut && get_duration() != 0 && !paused) { int64_t up_time = now - m_notification_start; - if (up_time >= m_data.duration * 1000) { + if (up_time >= get_duration() * 1000) { m_state = EState::FadingOut; m_fading_start = now; } else { - m_next_render = m_data.duration * 1000 - up_time; + m_next_render = get_duration() * 1000 - up_time; } } // Timers when fading @@ -626,53 +630,6 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 return false; } -NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : - NotificationManager::PopNotification(n, id_provider, evt_handler) -{ - set_large(large); -} -void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) -{ - if (!m_is_large) - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - else { - ImVec2 win_size(win_size_x, win_size_y); - ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); - float x_offset = m_left_indentation; - std::string fulltext = m_text1 + m_hypertext + m_text2; - ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); - float cursor_y = win_size.y / 2 - text_size.y / 2; - if (m_has_print_info) { - x_offset = 20; - cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_print_info.c_str()); - cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; - } - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_text1.c_str()); - - render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); - } -} -void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const std::string &info) -{ - m_print_info = info; - m_has_print_info = true; - if (m_is_large) - m_lines_count = 2; -} -void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) -{ - m_is_large = l; - //FIXME this information should not be lost (change m_data?) -// m_counting_down = !l; - m_hypertext = l ? _u8L("Export G-Code.") : std::string(); - m_state = l ? EState::Shown : EState::Hidden; - init(); -} //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() { @@ -680,9 +637,9 @@ void NotificationManager::ExportFinishedNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -793,6 +750,9 @@ void NotificationManager::ProgressBarNotification::init() { PopNotification::init(); //m_lines_count++; + if (m_endlines.empty()) { + m_endlines.push_back(0); + } if(m_lines_count >= 2) { m_lines_count = 3; m_multiline = true; @@ -897,8 +857,17 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); - ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); + if (m_render_percentage) { + std::string text; + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + imgui.text(text.c_str()); + } } //------PrintHostUploadNotification---------------- void NotificationManager::PrintHostUploadNotification::init() @@ -915,7 +884,7 @@ void NotificationManager::PrintHostUploadNotification::count_spaces() m_left_indentation = m_line_height; if (m_uj_state == UploadJobState::PB_ERROR) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -1102,15 +1071,382 @@ void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWr imgui.text(text.c_str()); } +//------SlicingProgressNotificastion +void NotificationManager::SlicingProgressNotification::init() +{ + if (m_sp_state == SlicingProgressState::SP_PROGRESS) { + ProgressBarNotification::init(); + //if (m_state == EState::NotFading && m_percentage >= 1.0f) + // m_state = EState::Shown; + } + else { + PopNotification::init(); + } + +} +void NotificationManager::SlicingProgressNotification::set_progress_state(float percent) +{ + if (percent < 0.f) + set_progress_state(SlicingProgressState::SP_CANCELLED); + else if (percent >= 1.f) + set_progress_state(SlicingProgressState::SP_COMPLETED); + else + set_progress_state(SlicingProgressState::SP_PROGRESS, percent); +} +void NotificationManager::SlicingProgressNotification::set_progress_state(NotificationManager::SlicingProgressNotification::SlicingProgressState state, float percent/* = 0.f*/) +{ + switch (state) + { + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: + m_state = EState::Hidden; + set_percentage(-1); + m_has_print_info = false; + set_export_possible(false); + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: + set_percentage(percent); + m_has_cancel_button = true; + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED: + set_percentage(-1); + m_has_cancel_button = false; + m_has_print_info = false; + set_export_possible(false); + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + set_percentage(1); + m_has_cancel_button = false; + m_has_print_info = false; + // m_export_possible is important only for PROGRESS state, thus we can reset it here + set_export_possible(false); + break; + default: + break; + } + m_sp_state = state; +} +void NotificationManager::SlicingProgressNotification::set_status_text(const std::string& text) +{ + switch (m_sp_state) + { + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: + m_state = EState::Hidden; + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text + ".", m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; + update(data); + m_state = EState::NotFading; + } + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text }; + update(data); + m_state = EState::Shown; + } + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, _u8L("Slicing finished."), m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; + update(data); + m_state = EState::Shown; + } + break; + default: + break; + } +} +void NotificationManager::SlicingProgressNotification::set_print_info(const std::string& info) +{ + if (m_sp_state != SlicingProgressState::SP_COMPLETED) { + set_progress_state (SlicingProgressState::SP_COMPLETED); + } else { + m_has_print_info = true; + m_print_info = info; + } +} +void NotificationManager::SlicingProgressNotification::set_sidebar_collapsed(bool collapsed) +{ + m_sidebar_collapsed = collapsed; + if (m_sp_state == SlicingProgressState::SP_COMPLETED) + m_state = EState::Shown; +} + +void NotificationManager::SlicingProgressNotification::on_cancel_button() +{ + if (m_cancel_callback){ + m_cancel_callback(); + } +} +int NotificationManager::SlicingProgressNotification::get_duration() +{ + if (m_sp_state == SlicingProgressState::SP_CANCELLED) + return 10; + else if (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed) + return 5; + else + return 0; +} +bool NotificationManager::SlicingProgressNotification::update_state(bool paused, const int64_t delta) +{ + bool ret = ProgressBarNotification::update_state(paused, delta); + // sets Estate to hidden + if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) + set_progress_state(SlicingProgressState::SP_NO_SLICING); + return ret; +} +void NotificationManager::SlicingProgressNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed)) { + ProgressBarNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + /* // enable for hypertext during slicing (correct call of export_enabled needed) + if (m_multiline) { + // two lines text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + imgui.text(line.c_str()); + if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { + ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); + render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height + m_line_height / 4, m_hypertext); + } + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + else { + //one line text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + std::string line = m_text1.substr(0, m_endlines[0]); + imgui.text(line.c_str()); + if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { + ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); + render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height / 4, m_hypertext); + } + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + */ + } else if (m_sp_state == SlicingProgressState::SP_COMPLETED) { + // "Slicing Finished" on line 1 + hypertext, print info on line + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext + m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + float cursor_y = win_size.y / 2 - text_size.y / 2; + if (m_sidebar_collapsed && m_has_print_info) { + x_offset = 20; + cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_print_info.c_str()); + cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; + } + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + if (m_sidebar_collapsed) + render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); + } else { + PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} +void NotificationManager::SlicingProgressNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (!(m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed))) { + return; + } + //std::string text; + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + /* + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + imgui.text(text.c_str()); + */ +} +void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + on_cancel_button(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + on_cancel_button(); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} + +void NotificationManager::SlicingProgressNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + // Do not render close button while showing progress - cancel button is rendered instead + if (m_sp_state != SlicingProgressState::SP_PROGRESS) { + ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} +//------ProgressIndicatorNotification------- +void NotificationManager::ProgressIndicatorNotification::set_status_text(const char* text) +{ + NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 0, text }; + update(data); +} + +void NotificationManager::ProgressIndicatorNotification::init() +{ + // skip ProgressBarNotification::init (same code here) + PopNotification::init(); + if (m_endlines.empty()) { + m_endlines.push_back(0); + } + if (m_lines_count >= 2) { + m_lines_count = 3; + m_multiline = true; + while (m_endlines.size() < 3) + m_endlines.push_back(m_endlines.back()); + } + else { + m_lines_count = 2; + m_endlines.push_back(m_endlines.back()); + } + switch (m_progress_state) + { + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_HIDDEN: + m_state = EState::Hidden; + break; + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_REQUEST: + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_UPDATED: + m_state = EState::NotFading; + break; + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_COMPLETED: + m_state = EState::Shown; + break; + default: + break; + } +} +void NotificationManager::ProgressIndicatorNotification::set_percentage(float percent) +{ + ProgressBarNotification::set_percentage(percent); + if (percent >= 0.0f && percent < 1.0f) { + m_state = EState::NotFading; + m_has_cancel_button = true; + m_progress_state = ProgressIndicatorState::PIS_PROGRESS_REQUEST; + } else if (percent >= 1.0f) { + m_state = EState::Shown; + m_progress_state = ProgressIndicatorState::PIS_COMPLETED; + m_has_cancel_button = false; + } else { + m_progress_state = ProgressIndicatorState::PIS_HIDDEN; + m_state = EState::Hidden; + } +} +bool NotificationManager::ProgressIndicatorNotification::update_state(bool paused, const int64_t delta) +{ + if (m_progress_state == ProgressIndicatorState::PIS_PROGRESS_REQUEST) { + // percentage was changed (and it called schedule_extra_frame), now update must know this needs render + m_next_render = 0; + m_progress_state = ProgressIndicatorState::PIS_PROGRESS_UPDATED; + return true; + } + bool ret = ProgressBarNotification::update_state(paused, delta); + if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) + // go to PIS_HIDDEN state + set_percentage(-1.0f); + return ret; +} + +void NotificationManager::ProgressIndicatorNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + on_cancel_button(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + on_cancel_button(); + } + ImGui::PopStyleColor(5); +} +void NotificationManager::ProgressIndicatorNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + // Do not render close button while showing progress - cancel button is rendered instead + if (m_percentage >= 1.0f) + { + ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : m_evt_handler(evt_handler) { } -NotificationManager::~NotificationManager() -{ - HintDatabase::get_instance().uninit(); -} + void NotificationManager::push_notification(const NotificationType type, int timestamp) { auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), @@ -1121,7 +1457,7 @@ void NotificationManager::push_notification(const NotificationType type, int tim } void NotificationManager::push_notification(const std::string& text, int timestamp) { - push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp); + push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotificationLevel, 10, text }, timestamp); } void NotificationManager::push_notification(NotificationType type, @@ -1133,11 +1469,11 @@ void NotificationManager::push_notification(NotificationType type, { int duration = 0; switch (level) { - case NotificationLevel::RegularNotification: duration = 10; break; - case NotificationLevel::ErrorNotification: break; - case NotificationLevel::WarningNotification: break; - case NotificationLevel::ImportantNotification: break; - case NotificationLevel::ProgressBarNotification: break; + case NotificationLevel::RegularNotificationLevel: duration = 10; break; + case NotificationLevel::ErrorNotificationLevel: break; + case NotificationLevel::WarningNotificationLevel: break; + case NotificationLevel::ImportantNotificationLevel: break; + case NotificationLevel::ProgressBarNotificationLevel: break; default: assert(false); return; @@ -1146,18 +1482,19 @@ void NotificationManager::push_notification(NotificationType type, } void NotificationManager::push_validate_error_notification(const std::string& text) { - push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); + push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); + set_slicing_progress_hidden(); } void NotificationManager::push_slicing_error_notification(const std::string& text) { set_all_slicing_errors_gray(false); - push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); - close_notification_of_type(NotificationType::SlicingComplete); + push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); + set_slicing_progress_hidden(); } void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step) { - NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; @@ -1168,7 +1505,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t } void NotificationManager::push_plater_error_notification(const std::string& text) { - push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); + push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); } void NotificationManager::close_plater_error_notification(const std::string& text) @@ -1192,7 +1529,7 @@ void NotificationManager::push_plater_warning_notification(const std::string& te } } - NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); push_notification_data(std::move(notification), 0); @@ -1252,49 +1589,12 @@ void NotificationManager::close_slicing_error_notification(const std::string& te } void NotificationManager::push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) { - NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotification, 0, text, hypertext, callback }; + NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotificationLevel, 0, text, hypertext, callback }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = object_id; notification->warning_step = 0; push_notification_data(std::move(notification), 0); } -void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) -{ - std::string hypertext; - int time = 10; - if (has_slicing_error_notification()) - return; - if (large) { - hypertext = _u8L("Export G-Code."); - time = 0; - } - NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext, - [](wxEvtHandler* evnthndlr){ - if (evnthndlr != nullptr) - wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); - return true; - } - }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, large), timestamp); -} -void NotificationManager::set_slicing_complete_print_time(const std::string &info) -{ - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingComplete) { - dynamic_cast(notification.get())->set_print_info(info); - break; - } - } -} -void NotificationManager::set_slicing_complete_large(bool large) -{ - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingComplete) { - dynamic_cast(notification.get())->set_large(large); - break; - } - } -} void NotificationManager::close_notification_of_type(const NotificationType type) { for (std::unique_ptr ¬ification : m_pop_notifications) { @@ -1324,7 +1624,7 @@ void NotificationManager::remove_object_warnings_of_released_objects(const std:: void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) { close_notification_of_type(NotificationType::ExportFinished); - NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; + NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } @@ -1338,7 +1638,7 @@ void NotificationManager::push_upload_job_notification(int id, float filesize, } } std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); - NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); } void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) @@ -1380,6 +1680,153 @@ void NotificationManager::upload_job_notification_show_error(int id, const std:: } } } + +void NotificationManager::init_slicing_progress_notification(std::function cancel_callback) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_cancel_callback(cancel_callback); + return; + } + } + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, std::string(),std::string(), + [](wxEvtHandler* evnthndlr) { + if (evnthndlr != nullptr) + wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); + return true; + } + }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, cancel_callback), 0); +} +void NotificationManager::set_slicing_progress_percentage(const std::string& text, float percentage) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + SlicingProgressNotification* spn = dynamic_cast(notification.get()); + spn->set_progress_state(percentage); + spn->set_status_text(text); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } + // Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended + wxGetApp().plater()->init_notification_manager(); +} + +void NotificationManager::set_slicing_progress_hidden() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + SlicingProgressNotification* notif = dynamic_cast(notification.get()); + notif->set_progress_state(SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } + // Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended + wxGetApp().plater()->init_notification_manager(); +} +void NotificationManager::set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_sidebar_collapsed(sidebar_colapsed); + dynamic_cast(notification.get())->set_print_info(info); + break; + } + } +} +void NotificationManager::set_sidebar_collapsed(bool collapsed) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_sidebar_collapsed(collapsed); + break; + } + } +} +void NotificationManager::set_fff(bool fff) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_fff(fff); + break; + } + } +} +void NotificationManager::set_slicing_progress_export_possible() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_export_possible(true); + break; + } + } +} +void NotificationManager::init_progress_indicator() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + return; + } + } + NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 2}; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + push_notification_data(std::move(notification), 0); +} + +void NotificationManager::progress_indicator_set_range(int range) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_range(range); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_cancel_callback(CancelFn callback/* = CancelFn()*/) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_cancel_callback(callback); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_progress(int pr) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_progress(pr); + // Ask for rendering - needs to be done on every progress. Calls to here doesnt trigger IDLE event or rendering. + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(100); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_status_text(const char* text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_status_text(text); + return; + } + } + init_progress_indicator(); +} +int NotificationManager::progress_indicator_get_range() const +{ + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + return dynamic_cast(notification.get())->get_range(); + } + } + return 0; +} + void NotificationManager::push_hint_notification(bool open_next) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -1389,7 +1836,7 @@ void NotificationManager::push_hint_notification(bool open_next) } } - NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" }; + NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::HintNotificationLevel, 300, "" }; // from user - open now if (!open_next) { push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), 0); @@ -1397,8 +1844,8 @@ void NotificationManager::push_hint_notification(bool open_next) // at startup - delay for half a second to let other notification pop up, than try every 30 seconds // show only if no notifications are shown } else { - auto condition = [this]() { - return this->get_notification_count() == 0; + auto condition = [&self = std::as_const(*this)]() { + return self.get_notification_count() == 0; }; push_delayed_notification(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); } @@ -1412,7 +1859,10 @@ bool NotificationManager::is_hint_notification_open() } return false; } - +void NotificationManager::deactivate_loaded_hints() +{ + HintDatabase::get_instance().uninit(); +} void NotificationManager::push_updated_item_info_notification(InfoItemType type) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -1422,7 +1872,7 @@ void NotificationManager::push_updated_item_info_notification(InfoItemType type) } } - NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 5, "" }; + NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotificationLevel, 5, "" }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler, type); if (push_notification_data(std::move(notification), 0)) { (dynamic_cast(m_pop_notifications.back().get()))->add_type(type); @@ -1444,17 +1894,20 @@ bool NotificationManager::push_notification_data(std::unique_ptrget_current_canvas3D(); - + bool retval = false; if (this->activate_existing(notification.get())) { - m_pop_notifications.back()->update(notification->get_data()); - canvas.schedule_extra_frame(0); - return false; + if (m_initialized) { // ignore update action - it cant be initialized if canvas and imgui context is not ready + m_pop_notifications.back()->update(notification->get_data()); + } } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.schedule_extra_frame(0); - return true; + retval = true; } + if (!m_initialized) + return retval; + GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + canvas.schedule_extra_frame(0); + return retval; } void NotificationManager::push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) @@ -1608,6 +2061,8 @@ void NotificationManager::set_in_preview(bool preview) notification->hide(preview); if (notification->get_type() == NotificationType::SignDetected) notification->hide(!preview); + if (m_in_preview && notification->get_type() == NotificationType::DidYouKnowHint) + notification->close(); } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index eb95aec580..76dc9a6887 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -6,6 +6,7 @@ #include "GLCanvas3D.hpp" #include "Event.hpp" #include "I18N.hpp" +#include "Jobs/ProgressIndicator.hpp" #include #include @@ -27,6 +28,8 @@ wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationCli using PresetUpdateAvailableClickedEvent = SimpleEvent; wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); +using CancelFn = std::function; + class GLCanvas3D; class ImGuiWrapper; enum class InfoItemType; @@ -34,9 +37,6 @@ enum class InfoItemType; enum class NotificationType { CustomNotification, - // Notification on end of slicing and G-code processing (the full G-code preview is available). - // Contains a hyperlink to export the G-code to a removable media. - SlicingComplete, // SlicingNotPossible, // Notification on end of export to a removable media, with hyperling to eject the external media. // Obsolete by ExportFinished @@ -78,6 +78,10 @@ enum class NotificationType ProgressBar, // Progress bar with info from Print Host Upload Queue dialog. PrintHostUpload, + // Progress bar with cancel button, cannot be closed + // On end of slicing and G-code processing (the full G-code preview is available), + // contains a hyperlink to export the G-code to a removable media or hdd. + SlicingProgress, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. EmptyColorChangeCode, // Notification that custom supports/seams were deleted after mesh repair. @@ -100,6 +104,8 @@ enum class NotificationType // Shows when ObjectList::update_info_items finds information that should be stressed to the user // Might contain logo taken from gizmos UpdatedItemsInfo, + // Progress bar notification with methods to replace ProgressIndicator class. + ProgressIndicator }; class NotificationManager @@ -109,27 +115,31 @@ public: { // The notifications will be presented in the order of importance, thus these enum values // are sorted by the importance. - // "Good to know" notification, usually but not always with a quick fade-out. - RegularNotification = 1, + // Important notification with progress bar, no fade-out, might appear again after closing. Position at the bottom. + ProgressBarNotificationLevel = 1, + // "Did you know" notification with special icon and buttons, Position close to bottom. + HintNotificationLevel, + // "Good to know" notification, usually but not always with a quick fade-out. + RegularNotificationLevel, // Information notification without a fade-out or with a longer fade-out. - ImportantNotification, - // Important notification with progress bar, no fade-out, might appear again after closing. - ProgressBarNotification, + ImportantNotificationLevel, // Warning, no fade-out. - WarningNotification, - // Error, no fade-out. - ErrorNotification, + WarningNotificationLevel, + // Error, no fade-out. Top most position. + ErrorNotificationLevel, }; NotificationManager(wxEvtHandler* evt_handler); - ~NotificationManager(); + ~NotificationManager(){} + // init is called after canvas3d is created. Notifications added before init are not showed or updated + void init() { m_initialized = true; } // Push a prefabricated notification from basic_notifications (see the table at the end of this file). void push_notification(const NotificationType type, int timestamp = 0); - // Push a NotificationType::CustomNotification with NotificationLevel::RegularNotification and 10s fade out interval. + // Push a NotificationType::CustomNotification with NotificationLevel::RegularNotificationLevel and 10s fade out interval. void push_notification(const std::string& text, int timestamp = 0); - // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification. - // ErrorNotification and ImportantNotification are never faded out. + // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotificationLevel. + // ErrorNotificationLevel and ImportantNotificationLevel are never faded out. void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", std::function callback = std::function(), int timestamp = 0); // Creates Validate Error notification with a custom text and no fade out. @@ -162,25 +172,44 @@ public: // Close object warnings, whose ObjectID is not in the list. // living_oids is expected to be sorted. void remove_object_warnings_of_released_objects(const std::vector& living_oids); - // Creates special notification slicing complete. - // If large = true (Plater side bar is closed), then printing time and export button is shown - // at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s. - void push_slicing_complete_notification(int timestamp, bool large); - // Add a print time estimate to an existing SlicingComplete notification. - void set_slicing_complete_print_time(const std::string &info); // Called when the side bar changes its visibility, as the "slicing complete" notification supplements // the "slicing info" normally shown at the side bar. - void set_slicing_complete_large(bool large); + void set_sidebar_collapsed(bool collapsed); + // Set technology for correct text in SlicingProgress. + void set_fff(bool b); + void set_fdm(bool b) { set_fff(b); } + void set_sla(bool b) { set_fff(!b); } // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); - // notification with progress bar + // notifications with progress bar + // print host upload void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); + // slicing progress + void init_slicing_progress_notification(std::function cancel_callback); + // percentage negative = canceled, <0-1) = progress, 1 = completed + void set_slicing_progress_percentage(const std::string& text, float percentage); + // hides slicing progress notification imidietly + void set_slicing_progress_hidden(); + // Add a print time estimate to an existing SlicingProgress notification. Set said notification to SP_COMPLETED state. + void set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed); + void set_slicing_progress_export_possible(); + // ProgressIndicator notification + // init adds hidden instance of progress indi notif that should always live (goes to hidden instead of erasing) + void init_progress_indicator(); + // functions equal to ProgressIndicator class + void progress_indicator_set_range(int range); + void progress_indicator_set_cancel_callback(CancelFn callback = CancelFn()); + void progress_indicator_set_progress(int pr); + void progress_indicator_set_status_text(const char*); // utf8 char array + int progress_indicator_get_range() const; // Hint (did you know) notification void push_hint_notification(bool open_next); bool is_hint_notification_open(); + // Forces Hints to reload its content when next hint should be showed + void deactivate_loaded_hints(); void push_updated_item_info_notification(InfoItemType type); // Close old notification ExportFinished. void new_export_began(bool on_removable); @@ -267,7 +296,7 @@ private: virtual bool compare_text(const std::string& text) const; void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } // sets m_next_render with time of next mandatory rendering. Delta is time since last render. - bool update_state(bool paused, const int64_t delta); + virtual bool update_state(bool paused, const int64_t delta); int64_t next_render() const { return is_finished() ? 0 : m_next_render; } EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } @@ -303,6 +332,8 @@ private: virtual void count_lines(); // returns true if PopStyleColor should be called later to pop this push virtual bool push_background_color(); + // used this function instead of reading directly m_data.duration. Some notifications might need to return changing value. + virtual int get_duration() { return m_data.duration; } const NotificationData m_data; // For reusing ImGUI windows. @@ -359,29 +390,7 @@ private: wxEvtHandler* m_evt_handler; }; - class SlicingCompleteLargeNotification : public PopNotification - { - public: - SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); - void set_large(bool l); - bool get_large() { return m_is_large; } - void set_print_info(const std::string &info); - void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override - { - // This notification is always hidden if !large (means side bar is collapsed) - if (!get_large() && !is_finished()) - m_state = EState::Hidden; - PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); - } - protected: - void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) - override; - bool m_is_large; - bool m_has_print_info { false }; - std::string m_print_info; - }; + class SlicingWarningNotification : public PopNotification { @@ -405,7 +414,7 @@ private: { public: - ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { } + ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { } virtual void set_percentage(float percent) { m_percentage = percent; } protected: virtual void init() override; @@ -423,9 +432,10 @@ private: {} void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override {} - float m_percentage; + float m_percentage {0.0f}; bool m_has_cancel_button {false}; + bool m_render_percentage {false}; // local time of last hover for showing tooltip }; @@ -443,7 +453,7 @@ private: PB_COMPLETED }; PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) - :ProgressBarNotification(n, id_provider, evt_handler, percentage) + :ProgressBarNotification(n, id_provider, evt_handler) , m_job_id(job_id) , m_file_size(filesize) { @@ -472,7 +482,117 @@ private: // Size of uploaded size to be displayed in MB float m_file_size; long m_hover_time{ 0 }; - UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; + UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; + }; + + class SlicingProgressNotification : public ProgressBarNotification + { + public: + // Inner state of notification, Each state changes bahaviour of the notification + enum class SlicingProgressState + { + SP_NO_SLICING, // hidden + SP_PROGRESS, // never fades outs, no close button, has cancel button + SP_CANCELLED, // fades after 10 seconds, simple message + SP_COMPLETED // Has export hyperlink and print info, fades after 20 sec if sidebar is shown, otherwise no fade out + }; + SlicingProgressNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, std::function callback) + : ProgressBarNotification(n, id_provider, evt_handler) + , m_cancel_callback(callback) + { + set_progress_state(SlicingProgressState::SP_NO_SLICING); + m_has_cancel_button = false; + m_render_percentage = true; + } + // sets text of notification - call after setting progress state + void set_status_text(const std::string& text); + // sets cancel button callback + void set_cancel_callback(std::function callback) { m_cancel_callback = callback; } + bool has_cancel_callback() const { return m_cancel_callback != nullptr; } + // sets SlicingProgressState, negative percent means canceled + void set_progress_state(float percent); + // sets SlicingProgressState, percent is used only at progress state. + void set_progress_state(SlicingProgressState state,float percent = 0.f); + // sets additional string of print info and puts notification into Completed state. + void set_print_info(const std::string& info); + // sets fading if in Completed state. + void set_sidebar_collapsed(bool collapsed); + // Calls inherited update_state and ensures Estate goes to hidden not closing. + bool update_state(bool paused, const int64_t delta) override; + // Switch between technology to provide correct text. + void set_fff(bool b) { m_is_fff = b; } + void set_fdm(bool b) { m_is_fff = b; } + void set_sla(bool b) { m_is_fff = !b; } + void set_export_possible(bool b) { m_export_possible = b; } + protected: + void init() override; + void count_lines() override + { + if (m_sp_state == SlicingProgressState::SP_PROGRESS) + ProgressBarNotification::count_lines(); + else + PopNotification::count_lines(); + } + void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; + void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void on_cancel_button(); + int get_duration() override; + std::function m_cancel_callback; + SlicingProgressState m_sp_state { SlicingProgressState::SP_PROGRESS }; + bool m_has_print_info { false }; + std::string m_print_info; + bool m_sidebar_collapsed { false }; + bool m_is_fff { true }; + // if true, it is possible show export hyperlink in state SP_PROGRESS + bool m_export_possible { false }; + }; + + class ProgressIndicatorNotification : public ProgressBarNotification + { + public: + enum class ProgressIndicatorState + { + PIS_HIDDEN, // hidden + PIS_PROGRESS_REQUEST, // progress was updated, request render on next update_state() call + PIS_PROGRESS_UPDATED, // render was requested + PIS_COMPLETED // both completed and canceled state. fades out into PIS_NO_SLICING + }; + ProgressIndicatorNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) + : ProgressBarNotification(n, id_provider, evt_handler) + { + m_render_percentage = true; + } + // ProgressIndicator + void set_range(int range) { m_range = range; } + void set_cancel_callback(CancelFn callback) { m_cancel_callback = callback; } + void set_progress(int pr) { set_percentage((float)pr / (float)m_range); } + void set_status_text(const char*); // utf8 char array + int get_range() const { return m_range; } + // ProgressBarNotification + void init() override; + void set_percentage(float percent) override; + bool update_state(bool paused, const int64_t delta) override; + // Own + protected: + int m_range { 100 }; + CancelFn m_cancel_callback { nullptr }; + ProgressIndicatorState m_progress_state { ProgressIndicatorState::PIS_HIDDEN }; + + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void on_cancel_button() { if (m_cancel_callback) m_cancel_callback(); } }; class ExportFinishedNotification : public PopNotification @@ -499,7 +619,7 @@ private: void render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_eject_button(ImGuiWrapper& imgui, + void render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override @@ -562,13 +682,15 @@ private: // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); + // set by init(), until false notifications are only added not updated and frame is not requested after push + bool m_initialized{ false }; // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; // Cache of IDs to identify and reuse ImGUI windows. NotificationIDProvider m_id_provider; std::deque> m_pop_notifications; // delayed waiting notifications, first is remaining time - std::deque m_waiting_notifications; + std::vector m_waiting_notifications; //timestamps used for slicing finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; // True if G-code preview is active. False if the Plater is active. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ac76fe6559..7d344c389b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1208,6 +1208,8 @@ void Sidebar::update_sliced_info_sizer() wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time)); p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":"); + p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed()); + // Hide non-SLA sliced info parameters p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A"); @@ -1296,10 +1298,7 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); - // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate - //if (p->plater->is_sidebar_collapsed()) - p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); - p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed()); } if (ps.estimated_silent_print_time != "N/A") { @@ -1502,7 +1501,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; - NotificationManager* notification_manager { nullptr }; + std::shared_ptr notification_manager; ProjectDirtyStateManager dirty_state; @@ -1523,10 +1522,10 @@ struct Plater::priv public: Jobs(priv *_m) : m(_m) { - m_arrange_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_fill_bed_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_sla_import_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_arrange_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_fill_bed_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_rotoptimize_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_sla_import_id = add_job(std::make_unique(m->notification_manager, m->q)); } void arrange() @@ -1637,7 +1636,7 @@ struct Plater::priv void apply_free_camera_correction(bool apply = true); void update_ui_from_settings(); void update_main_toolbar_tooltips(); - std::shared_ptr statusbar(); +// std::shared_ptr statusbar(); std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; @@ -1788,6 +1787,8 @@ struct Plater::priv // extension should contain the leading dot, i.e.: ".3mf" wxString get_project_filename(const wxString& extension = wxEmptyString) const; void set_project_filename(const wxString& filename); + // Call after plater and Canvas#D is initialized + void init_notification_manager(); // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it. mutable bool ready_to_slice = { false }; @@ -1797,6 +1798,7 @@ struct Plater::priv std::string last_output_dir_path; bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } bool process_completed_with_error { false }; + private: bool layers_height_allowed() const; @@ -1845,6 +1847,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) + , notification_manager(std::make_shared(q)) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") @@ -2012,7 +2015,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); #endif /* _WIN32 */ - notification_manager = new NotificationManager(this->q); + //notification_manager = new NotificationManager(this->q); + if (wxGetApp().is_editor()) { this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); @@ -2022,12 +2026,12 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->show_action_buttons(this->ready_to_slice); notification_manager->close_notification_of_type(NotificationType::ExportFinished); notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) ); } else { notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::ErrorNotification, + NotificationManager::NotificationLevel::ErrorNotificationLevel, format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) ); } @@ -2074,8 +2078,7 @@ Plater::priv::~priv() { if (config != nullptr) delete config; - if (notification_manager != nullptr) - delete notification_manager; + notification_manager->deactivate_loaded_hints(); } void Plater::priv::update(unsigned int flags) @@ -2150,6 +2153,8 @@ void Plater::priv::collapse_sidebar(bool collapse) new_tooltip += " [Shift+Tab]"; int id = collapse_toolbar.get_item_id("collapse_sidebar"); collapse_toolbar.set_tooltip(id, new_tooltip); + + notification_manager->set_sidebar_collapsed(collapse); } @@ -2177,10 +2182,11 @@ void Plater::priv::update_main_toolbar_tooltips() view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); } -std::shared_ptr Plater::priv::statusbar() -{ - return main_frame->m_statusbar; -} +//std::shared_ptr Plater::priv::statusbar() +//{ +// return nullptr; +// return main_frame->m_statusbar; +//} std::string Plater::priv::get_config(const std::string &key) const { @@ -2325,7 +2331,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ for (std::string& name : names) notif_text += "\n - " + name; notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, notif_text); + NotificationManager::NotificationLevel::RegularNotificationLevel, notif_text); } } @@ -2467,7 +2473,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (load_model) { wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string()); // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... - statusbar()->set_status_text(_L("Loaded")); +// statusbar()->set_status_text(_L("Loaded")); } // automatic selection of added objects @@ -2880,7 +2886,7 @@ void Plater::priv::split_object() // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("All non-solid parts (modifiers) were deleted")); Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); @@ -2956,7 +2962,7 @@ void Plater::priv::process_validation_warning(const std::string& warning) const notification_manager->push_notification( NotificationType::ValidateWarning, - NotificationManager::NotificationLevel::WarningNotification, + NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("WARNING:") + "\n" + text, hypertext, action_fn ); } @@ -3002,6 +3008,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // In SLA mode, we need to reload the 3D scene every time to show the support structures. if (printer_technology == ptSLA || (printer_technology == ptFFF && config->opt_bool("wipe_tower"))) return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + + notification_manager->set_slicing_progress_hidden(); } if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! background_process.empty()) { @@ -3074,9 +3082,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool else { // Background data is valid. - if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || - (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) - this->statusbar()->set_status_text(_L("Ready to slice")); +// if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || +// (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) +// this->statusbar()->set_status_text(_L("Ready to slice")); sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); @@ -3113,10 +3121,10 @@ bool Plater::priv::restart_background_process(unsigned int state) (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { // The print is valid and it can be started. if (this->background_process.start()) { - this->statusbar()->set_cancel_callback([this]() { - this->statusbar()->set_status_text(_L("Cancelling")); - this->background_process.stop(); - }); +// this->statusbar()->set_cancel_callback([this]() { +// this->statusbar()->set_status_text(_L("Cancelling")); +// this->background_process.stop(); +// }); if (!show_warning_dialog) on_slicing_began(); return true; @@ -3753,9 +3761,9 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) return; } - this->statusbar()->set_progress(evt.status.percent); - this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); - //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f); +// this->statusbar()->set_progress(evt.status.percent); +// this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); + notification_manager->set_slicing_progress_percentage(evt.status.text, (float)evt.status.percent / 100.0f); } if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { @@ -3806,7 +3814,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { - notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed()); switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3829,8 +3836,8 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) void Plater::priv::on_slicing_began() { clear_warnings(); - notification_manager->close_notification_of_type(NotificationType::SlicingComplete); notification_manager->close_notification_of_type(NotificationType::SignDetected); + notification_manager->close_notification_of_type(NotificationType::ExportFinished); } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { @@ -3895,8 +3902,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // At this point of time the thread should be either finished or canceled, // so the following call just confirms, that the produced data were consumed. this->background_process.stop(); - this->statusbar()->reset_cancel_callback(); - this->statusbar()->stop_busy(); +// this->statusbar()->reset_cancel_callback(); +// this->statusbar()->stop_busy(); + notification_manager->set_slicing_progress_export_possible(); // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); @@ -3913,7 +3921,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) show_error(q, message.first, message.second); } else notification_manager->push_slicing_error_notification(message.first); - this->statusbar()->set_status_text(from_u8(message.first)); +// this->statusbar()->set_status_text(from_u8(message.first)); if (evt.invalidate_plater()) { const wxString invalid_str = _L("Invalid data"); @@ -3923,8 +3931,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) } has_error = true; } - if (evt.cancelled()) - this->statusbar()->set_status_text(_L("Cancelled")); + if (evt.cancelled()) { +// this->statusbar()->set_status_text(_L("Cancelled")); + this->notification_manager->set_slicing_progress_percentage(_utf8("Slicing Cancelled."), -1); + } this->sidebar->show_sliced_info_sizer(evt.success()); @@ -4128,6 +4138,20 @@ void Plater::priv::set_project_filename(const wxString& filename) wxGetApp().mainframe->add_to_recent_projects(filename); } +void Plater::priv::init_notification_manager() +{ + if (!notification_manager) + return; + notification_manager->init(); + + auto cancel_callback = [this]() { + this->background_process.stop(); + }; + notification_manager->init_slicing_progress_notification(cancel_callback); + notification_manager->set_fff(printer_technology == ptFFF); + notification_manager->init_progress_indicator(); +} + void Plater::priv::set_current_canvas_as_dirty() { if (current_panel == view3D) @@ -5566,7 +5590,7 @@ void Plater::export_stl(bool extended, bool selection_only) } Slic3r::store_stl(path_u8.c_str(), &mesh, true); - p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); } void Plater::export_amf() @@ -5583,10 +5607,10 @@ void Plater::export_amf() bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { // Success - p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); } else { // Failure - p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); } } @@ -5625,12 +5649,12 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); if (ret) { // Success - p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); p->set_project_filename(path); } else { // Failure - p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } return ret; } @@ -6204,6 +6228,8 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) p->sidebar->get_searcher().set_printer_technology(printer_technology); + p->notification_manager->set_fff(printer_technology == ptFFF); + return ret; } @@ -6224,7 +6250,7 @@ void Plater::clear_before_change_mesh(int obj_idx) // snapshot_time is captured by copy so the lambda knows where to undo/redo to. get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Custom supports, seams and multimaterial painting were " "removed after repairing the mesh.")); // _u8L("Undo the repair"), @@ -6237,7 +6263,7 @@ void Plater::clear_before_change_mesh(int obj_idx) // else // notification_manager->push_notification( // NotificationType::CustomSupportsAndSeamRemovedAfterRepair, -// NotificationManager::NotificationLevel::RegularNotification, +// NotificationManager::NotificationLevel::RegularNotificationLevel, // _u8L("Cannot undo to before the mesh repair!")); // return true; // }); @@ -6503,14 +6529,14 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } -const NotificationManager* Plater::get_notification_manager() const +std::shared_ptr Plater::get_notification_manager() { return p->notification_manager; } -NotificationManager* Plater::get_notification_manager() +void Plater::init_notification_manager() { - return p->notification_manager; + p->init_notification_manager(); } bool Plater::can_delete() const { return p->can_delete(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f1a493b0f0..4a98797e5d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -359,11 +359,11 @@ public: void set_bed_shape() const; void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; - const NotificationManager* get_notification_manager() const; - NotificationManager* get_notification_manager(); + std::shared_ptr get_notification_manager(); + void init_notification_manager(); void bring_instance_forward(); - + // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots { diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 10c3911613..18485659c5 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -194,3 +194,4 @@ void ProgressStatusBar::hide_cancel_button() } } + diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 73c046be6d..3ce3c85792 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -25,6 +25,7 @@ namespace Slic3r { * of the Slicer main window. It consists of a message area to the left and a * progress indication area to the right with an optional cancel button. */ + class ProgressStatusBar : public ProgressIndicator { wxStatusBar *self; // we cheat! It should be the base class but: perl! From 670ec06b971a96f2e2fb4953ca4f1eb2e9866db2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Sep 2021 15:31:03 +0200 Subject: [PATCH 07/23] "CANCEL" button is deleted from "Support Generator" MessageDialog to avoid confusions when "Supports mode" is switched from right panel --- src/slic3r/GUI/ConfigManipulation.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d4920d8364..070773c116 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -171,23 +171,14 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con "- Detect bridging perimeters")); if (is_global_config) msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?")); - //wxMessageDialog dialog(nullptr, msg_text, _(L("Support Generator")), - MessageDialog dialog(nullptr, msg_text, _(L("Support Generator")), - wxICON_WARNING | (is_global_config ? wxYES | wxNO | wxCANCEL : wxOK)); + MessageDialog dialog(nullptr, msg_text, _L("Support Generator"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); DynamicPrintConfig new_conf = *config; auto answer = dialog.ShowModal(); if (!is_global_config || answer == wxID_YES) { // Enable "detect bridging perimeters". new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); } - else if (answer == wxID_NO) { - // Do nothing, leave supports on and "detect bridging perimeters" off. - } - else if (answer == wxID_CANCEL) { - // Disable supports. - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - support_material_overhangs_queried = false; - } + //else Do nothing, leave supports on and "detect bridging perimeters" off. apply(config, &new_conf); } } From e3ac2a9e4556c766a636d876f00c03190ee5b41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sat, 11 Sep 2021 00:53:24 +0200 Subject: [PATCH 08/23] Fixed Perl unit tests after 34c4b74af49c12e89fc6b5cc2bcfa36c69797578. --- t/cooling.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/cooling.t b/t/cooling.t index 9ac5a24f4d..e46cfa2f77 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -34,9 +34,9 @@ sub buffer { $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); - my @extruders = shift; - @extruders = [ 0 ] if int(@extruders) == 0; - $gcodegen->set_extruders(\@extruders); + my $extruders_ref = shift; + $extruders_ref = [ 0 ] if !defined $extruders_ref; + $gcodegen->set_extruders($extruders_ref); return Slic3r::GCode::CoolingBuffer->new($gcodegen); } From ad65366ac7c4a8fa527bf87369d781029c858ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 9 Sep 2021 15:25:14 +0200 Subject: [PATCH 09/23] Added fast_float library as a replacement for std::from_chars and strtod. --- src/fast_float/fast_float.h | 2449 ++++++++++++++++++++++++++++++++ src/libslic3r/GCodeReader.cpp | 11 +- src/slic3r/GUI/AboutDialog.cpp | 4 +- 3 files changed, 2454 insertions(+), 10 deletions(-) create mode 100644 src/fast_float/fast_float.h diff --git a/src/fast_float/fast_float.h b/src/fast_float/fast_float.h new file mode 100644 index 0000000000..8974005319 --- /dev/null +++ b/src/fast_float/fast_float.h @@ -0,0 +1,2449 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes + + +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini + + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include + +namespace fast_float { +enum chars_format { + scientific = 1<<0, + fixed = 1<<2, + hex = 1<<3, + general = fixed | scientific +}; + + +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +struct parse_options { + explicit parse_options(chars_format fmt = chars_format::general, + char dot = '.') + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + char decimal_point; +}; + +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allowe the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept; + +} +#endif // FASTFLOAT_FAST_FLOAT_H + + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_64BIT +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) \ + || defined(__MINGW32__)) +#define FASTFLOAT_32BIT +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#ifdef _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#else +#include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +namespace fast_float { + +// Compares two ASCII strings in a case insensitive manner. +inline bool fastfloat_strncasecmp(const char *input1, const char *input2, + size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; i++) { + running_diff |= (input1[i] ^ input2[i]); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +namespace { +constexpr uint32_t max_digits = 768; +constexpr uint32_t max_digit_without_overflow = 19; +constexpr int32_t decimal_point_range = 2047; +} // namespace + +struct value128 { + uint64_t low; + uint64_t high; + value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + value128() : low(0), high(0) {} +}; + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + int last_bit = 0; + if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; + if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; + if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; + if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; + if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; + if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; + return 63 - last_bit; + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, + uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline value128 full_multiplication(uint64_t a, + uint64_t b) { + value128 answer; +#ifdef _M_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emulate + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + #error Not implemented +#endif + return answer; +} + + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +struct decimal { + uint32_t num_digits{0}; + int32_t decimal_point{0}; + bool negative{false}; + bool truncated{false}; + uint8_t digits[max_digits]; + decimal() = default; + // Copies are not allowed since this is a fat object. + decimal(const decimal &) = delete; + // Copies are not allowed since this is a fat object. + decimal &operator=(const decimal &) = delete; + // Moves are allowed: + decimal(decimal &&) = default; + decimal &operator=(decimal &&other) = default; +}; + +constexpr static double powers_of_ten_double[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; +constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, + 1e6, 1e7, 1e8, 1e9, 1e10}; + +template struct binary_format { + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(); + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); +}; + +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { return 63; } +template <> inline constexpr int binary_format::sign_index() { return 31; } + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + return powers_of_ten_double[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + + return powers_of_ten_float[power]; +} + + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + +} // namespace fast_float + +// for convenience: +template +inline OStream& operator<<(OStream &out, const fast_float::decimal &d) { + out << "0."; + for (size_t i = 0; i < d.num_digits; i++) { + out << int32_t(d.digits[i]); + } + out << " * 10 ** " << d.decimal_point; + return out; +} + +#endif + + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +struct parsed_number_string { + int64_t exponent; + uint64_t mantissa; + const char *lastmatch; + bool negative; + bool valid; + bool too_many_digits; +}; + + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + // Fast approach only tested under little endian systems + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = end_of_integer_part + 1 - p; + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + i = 0; + p = start_digits; + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p++; // skip the dot + const char *first_after_period = p; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = first_after_period - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + + +// This should always succeed since it follows a call to parse_number_string +// This function could be optimized. In particular, we could stop after 19 digits +// and try to bail out. Furthermore, we should be able to recover the computed +// exponent from the pass in parse_number_string. +fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { + const char decimal_point = options.decimal_point; + + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + } + // skip leading zeroes + while ((p != pend) && (*p == '0')) { + ++p; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if(answer.num_digits == 0) { + // skip zeros + while ((p != pend) && (*p == '0')) { + ++p; + } + } + // We expect that this loop will often take the bulk of the running time + // because when a value has lots of digits, these digits often + while ((p + 8 <= pend) && (answer.num_digits + 8 < max_digits)) { + uint64_t val = read_u64(p); + if(! is_made_of_eight_digits_fast(val)) { break; } + // We have eight digits, process them in one go! + val -= 0x3030303030303030; + write_u64(answer.digits + answer.num_digits, val); + answer.num_digits += 8; + p += 8; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + // We want num_digits to be the number of significant digits, excluding + // leading *and* trailing zeros! Otherwise the truncated flag later is + // going to be misleading. + if(answer.num_digits > 0) { + // We potentially need the answer.num_digits > 0 guard because we + // prune leading zeros. So with answer.num_digits > 0, we know that + // we have at least one non-zero digit. + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == decimal_point)) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits) { + answer.truncated = true; + answer.num_digits = max_digits; + } + if ((p != pend) && (('e' == *p) || ('E' == *p))) { + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + int32_t exp_number = 0; // exponential part + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + // In very rare cases, we may have fewer than 19 digits, we want to be able to reliably + // assume that all digits up to max_digit_without_overflow have been initialized. + for(uint32_t i = answer.num_digits; i < max_digit_without_overflow; i++) { answer.digits[i] = 0; } + + return answer; +} +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^−1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +static const uint64_t power_of_five_128[number_of_entries]; +}; + +template +const uint64_t powers_template::power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +using powers = powers_template<>; + +} + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template +fastfloat_really_inline +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + fastfloat_really_inline int power(int q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template +fastfloat_really_inline +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation(q, w); + if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further + // In some very rare cases, this could happen, in which case we might need a more accurate + // computation that what we can provide cheaply. This is very, very unlikely. + // + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. + if(!inside_safe_exponent) { + answer.power2 = -1; // This (a negative value) indicates an error condition. + return answer; + } + } + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int(detail::power(int(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +struct parsed_number_string { + int64_t exponent; + uint64_t mantissa; + const char *lastmatch; + bool negative; + bool valid; + bool too_many_digits; +}; + + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + // Fast approach only tested under little endian systems + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = end_of_integer_part + 1 - p; + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + i = 0; + p = start_digits; + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p++; // skip the dot + const char *first_after_period = p; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = first_after_period - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + + +// This should always succeed since it follows a call to parse_number_string +// This function could be optimized. In particular, we could stop after 19 digits +// and try to bail out. Furthermore, we should be able to recover the computed +// exponent from the pass in parse_number_string. +fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { + const char decimal_point = options.decimal_point; + + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + } + // skip leading zeroes + while ((p != pend) && (*p == '0')) { + ++p; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if(answer.num_digits == 0) { + // skip zeros + while ((p != pend) && (*p == '0')) { + ++p; + } + } + // We expect that this loop will often take the bulk of the running time + // because when a value has lots of digits, these digits often + while ((p + 8 <= pend) && (answer.num_digits + 8 < max_digits)) { + uint64_t val = read_u64(p); + if(! is_made_of_eight_digits_fast(val)) { break; } + // We have eight digits, process them in one go! + val -= 0x3030303030303030; + write_u64(answer.digits + answer.num_digits, val); + answer.num_digits += 8; + p += 8; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + // We want num_digits to be the number of significant digits, excluding + // leading *and* trailing zeros! Otherwise the truncated flag later is + // going to be misleading. + if(answer.num_digits > 0) { + // We potentially need the answer.num_digits > 0 guard because we + // prune leading zeros. So with answer.num_digits > 0, we know that + // we have at least one non-zero digit. + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == decimal_point)) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits) { + answer.truncated = true; + answer.num_digits = max_digits; + } + if ((p != pend) && (('e' == *p) || ('E' == *p))) { + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + int32_t exp_number = 0; // exponential part + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + // In very rare cases, we may have fewer than 19 digits, we want to be able to reliably + // assume that all digits up to max_digit_without_overflow have been initialized. + for(uint32_t i = answer.num_digits; i < max_digit_without_overflow; i++) { answer.digits[i] = 0; } + + return answer; +} +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H +#define FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H + +/** + * This code is meant to handle the case where we have more than 19 digits. + * + * It is based on work by Nigel Tao (at https://github.com/google/wuffs/) + * who credits Ken Thompson for the design (via a reference to the Go source + * code). + * + * Rob Pike suggested that this algorithm be called "Simple Decimal Conversion". + * + * It is probably not very fast but it is a fallback that should almost never + * be used in real life. Though it is not fast, it is "easily" understood and debugged. + **/ +#include + +namespace fast_float { + +namespace detail { + +// remove all final zeroes +inline void trim(decimal &h) { + while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { + h.num_digits--; + } +} + + + +inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t shift) { + shift &= 63; + const static uint16_t number_of_digits_decimal_left_shift_table[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, + 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, + 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, + 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, + 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, + 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, + 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, + 0x051C, 0x051C, + }; + uint32_t x_a = number_of_digits_decimal_left_shift_table[shift]; + uint32_t x_b = number_of_digits_decimal_left_shift_table[shift + 1]; + uint32_t num_new_digits = x_a >> 11; + uint32_t pow5_a = 0x7FF & x_a; + uint32_t pow5_b = 0x7FF & x_b; + const static uint8_t + number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, + 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, + 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, + 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, 5, 2, 5, 8, + 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6, + 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, + 3, 6, 7, 4, 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, + 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, + 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6, 4, 4, 7, 7, 5, 3, + 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, + 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, + 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, + 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, + 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, + 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, + 5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, + 1, 1, 6, 4, 1, 5, 3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, + 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, + 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, + 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, + 6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, + 0, 3, 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, + 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, + 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, 2, + 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5, + 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, + 3, 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, + 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, + 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, 8, 6, 0, 8, 0, 8, + 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0, + 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, + 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, + 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1, 0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, + 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, + 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, + 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, + 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, + 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, + 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, + 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, + 9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, + 0, 6, 2, 5, 1, 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, + 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, + 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, + 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1, + 3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, + 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, + 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, + 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, 7, 6, 2, + 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1, + 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, + 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, + 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, + 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2, 2, 4, 0, 6, 9, 5, + 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, + }; + const uint8_t *pow5 = + &number_of_digits_decimal_left_shift_table_powers_of_5[pow5_a]; + uint32_t i = 0; + uint32_t n = pow5_b - pow5_a; + for (; i < n; i++) { + if (i >= h.num_digits) { + return num_new_digits - 1; + } else if (h.digits[i] == pow5[i]) { + continue; + } else if (h.digits[i] < pow5[i]) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + return num_new_digits; +} + +inline uint64_t round(decimal &h) { + if ((h.num_digits == 0) || (h.decimal_point < 0)) { + return 0; + } else if (h.decimal_point > 18) { + return UINT64_MAX; + } + // at this point, we know that h.decimal_point >= 0 + uint32_t dp = uint32_t(h.decimal_point); + uint64_t n = 0; + for (uint32_t i = 0; i < dp; i++) { + n = (10 * n) + ((i < h.num_digits) ? h.digits[i] : 0); + } + bool round_up = false; + if (dp < h.num_digits) { + round_up = h.digits[dp] >= 5; // normally, we round up + // but we may need to round to even! + if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { + round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); + } + } + if (round_up) { + n++; + } + return n; +} + +// computes h * 2^-shift +inline void decimal_left_shift(decimal &h, uint32_t shift) { + if (h.num_digits == 0) { + return; + } + uint32_t num_new_digits = number_of_digits_decimal_left_shift(h, shift); + int32_t read_index = int32_t(h.num_digits - 1); + uint32_t write_index = h.num_digits - 1 + num_new_digits; + uint64_t n = 0; + + while (read_index >= 0) { + n += uint64_t(h.digits[read_index]) << shift; + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + read_index--; + } + while (n > 0) { + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + } + h.num_digits += num_new_digits; + if (h.num_digits > max_digits) { + h.num_digits = max_digits; + } + h.decimal_point += int32_t(num_new_digits); + trim(h); +} + +// computes h * 2^shift +inline void decimal_right_shift(decimal &h, uint32_t shift) { + uint32_t read_index = 0; + uint32_t write_index = 0; + + uint64_t n = 0; + + while ((n >> shift) == 0) { + if (read_index < h.num_digits) { + n = (10 * n) + h.digits[read_index++]; + } else if (n == 0) { + return; + } else { + while ((n >> shift) == 0) { + n = 10 * n; + read_index++; + } + break; + } + } + h.decimal_point -= int32_t(read_index - 1); + if (h.decimal_point < -decimal_point_range) { // it is zero + h.num_digits = 0; + h.decimal_point = 0; + h.negative = false; + h.truncated = false; + return; + } + uint64_t mask = (uint64_t(1) << shift) - 1; + while (read_index < h.num_digits) { + uint8_t new_digit = uint8_t(n >> shift); + n = (10 * (n & mask)) + h.digits[read_index++]; + h.digits[write_index++] = new_digit; + } + while (n > 0) { + uint8_t new_digit = uint8_t(n >> shift); + n = 10 * (n & mask); + if (write_index < max_digits) { + h.digits[write_index++] = new_digit; + } else if (new_digit > 0) { + h.truncated = true; + } + } + h.num_digits = write_index; + trim(h); +} + +} // namespace detail + +template +adjusted_mantissa compute_float(decimal &d) { + adjusted_mantissa answer; + if (d.num_digits == 0) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + // At this point, going further, we can assume that d.num_digits > 0. + // + // We want to guard against excessive decimal point values because + // they can result in long running times. Indeed, we do + // shifts by at most 60 bits. We have that log(10**400)/log(2**60) ~= 22 + // which is fine, but log(10**299995)/log(2**60) ~= 16609 which is not + // fine (runs for a long time). + // + if(d.decimal_point < -324) { + // We have something smaller than 1e-324 which is always zero + // in binary64 and binary32. + // It should be zero. + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } else if(d.decimal_point >= 310) { + // We have something at least as large as 0.1e310 which is + // always infinite. + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + static const uint32_t max_shift = 60; + static const uint32_t num_powers = 19; + static const uint8_t decimal_powers[19] = { + 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // + 33, 36, 39, 43, 46, 49, 53, 56, 59, // + }; + int32_t exp2 = 0; + while (d.decimal_point > 0) { + uint32_t n = uint32_t(d.decimal_point); + uint32_t shift = (n < num_powers) ? decimal_powers[n] : max_shift; + detail::decimal_right_shift(d, shift); + if (d.decimal_point < -decimal_point_range) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + exp2 += int32_t(shift); + } + // We shift left toward [1/2 ... 1]. + while (d.decimal_point <= 0) { + uint32_t shift; + if (d.decimal_point == 0) { + if (d.digits[0] >= 5) { + break; + } + shift = (d.digits[0] < 2) ? 2 : 1; + } else { + uint32_t n = uint32_t(-d.decimal_point); + shift = (n < num_powers) ? decimal_powers[n] : max_shift; + } + detail::decimal_left_shift(d, shift); + if (d.decimal_point > decimal_point_range) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + exp2 -= int32_t(shift); + } + // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. + exp2--; + constexpr int32_t minimum_exponent = binary::minimum_exponent(); + while ((minimum_exponent + 1) > exp2) { + uint32_t n = uint32_t((minimum_exponent + 1) - exp2); + if (n > max_shift) { + n = max_shift; + } + detail::decimal_right_shift(d, n); + exp2 += int32_t(n); + } + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + + const int mantissa_size_in_bits = binary::mantissa_explicit_bits() + 1; + detail::decimal_left_shift(d, mantissa_size_in_bits); + + uint64_t mantissa = detail::round(d); + // It is possible that we have an overflow, in which case we need + // to shift back. + if(mantissa >= (uint64_t(1) << mantissa_size_in_bits)) { + detail::decimal_right_shift(d, 1); + exp2 += 1; + mantissa = detail::round(d); + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + } + answer.power2 = exp2 - binary::minimum_exponent(); + if(mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) { answer.power2--; } + answer.mantissa = mantissa & ((uint64_t(1) << binary::mantissa_explicit_bits()) - 1); + return answer; +} + +template +adjusted_mantissa parse_long_mantissa(const char *first, const char* last, parse_options options) { + decimal d = parse_decimal(first, last, options); + return compute_float(d); +} + +} // namespace fast_float +#endif + + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + +#include +#include +#include +#include +#include + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, "nan", 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == '(') { + for(const char* ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, "inf", 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +template +fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); + word = negative + ? word | (uint64_t(1) << binary_format::sign_index()) : word; +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + if (std::is_same::value) { + ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian + } else { + ::memcpy(&value, &word, sizeof(T)); + } +#else + // For little-endian systems: + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +} // namespace detail + + + +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options{fmt}); +} + +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept { + + static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); + + + from_chars_result answer; + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // Next is Clinger's fast path. + if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); + if(pns.too_many_digits) { + if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am.power2 = -1; // value is invalid. + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = parse_long_mantissa>(first, last, options); } + detail::to_float(pns.negative, am, value); + return answer; +} + +} // namespace fast_float + +#endif + diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 4c4bebee44..61ab10f223 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -10,6 +9,7 @@ #include "LocalesUtils.hpp" #include +#include namespace Slic3r { @@ -71,16 +71,9 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G } if (axis != NUM_AXES_WITH_UNKNOWN) { // Try to parse the numeric value. -#ifdef WIN32 double v; - auto [pend, ec] = std::from_chars(++ c, end, v); + auto [pend, ec] = fast_float::from_chars(++ c, end, v); if (pend != c && is_end_of_word(*pend)) { -#else - // The older version of GCC and Clang support std::from_chars just for integers, so strtod we used it instead. - char *pend = nullptr; - double v = strtod(++ c, &pend); - if (pend != nullptr && is_end_of_word(*pend)) { -#endif // The axis value has been parsed correctly. if (axis != UNKNOWN_AXIS) gline.m_axis[int(axis)] = float(v); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 72f1421f68..2aae9270dc 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -122,7 +122,9 @@ void CopyrightsDialog::fill_entries() { "AppImage packaging for Linux using AppImageKit" , "2004-2019 Simon Peter and contributors" , "https://appimage.org/" }, { "lib_fts" - , "Forrest Smith" , "https://www.forrestthewoods.com/" } + , "Forrest Smith" , "https://www.forrestthewoods.com/" }, + { "fast_float" + , "Daniel Lemire, João Paulo Magalhaes and contributors", "https://github.com/fastfloat/fast_float" } }; } From d2a185ddb69d4a65910016933ed283cc7fcd6926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 9 Sep 2021 15:45:31 +0200 Subject: [PATCH 10/23] Optimized export of floating-point value inside emit_axis. Change the behavior of generated G-code commands. Now all redundancy padding zeros are removed, which makes G-code a little bit smaller. --- src/libslic3r/GCodeWriter.cpp | 52 +++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 793a666752..eb45e95c1f 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -7,6 +7,10 @@ #include #include +#ifdef __APPLE__ + #include +#endif + #define XYZF_EXPORT_DIGITS 3 #define E_EXPORT_DIGITS 5 @@ -273,17 +277,47 @@ public: } void emit_axis(const char axis, const double v, size_t digits) { - *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = axis; -#ifdef WIN32 - this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end, v, std::chars_format::fixed, digits); + assert(digits <= 6); + static constexpr const std::array pow_10{1, 10, 100, 1000, 10000, 100000, 1000000}; + *ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis; + + char *base_ptr = this->ptr_err.ptr; + auto v_int = int64_t(std::round(v * pow_10[digits])); + // Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it. + // That is a little bit slower than std::to_chars but not much. +#ifdef __APPLE__ + boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator(), v_int); #else - int buf_capacity = int(this->buf_end - this->ptr_err.ptr); - int ret = snprintf(this->ptr_err.ptr, buf_capacity, "%.*lf", int(digits), v); - if (ret <= 0 || ret > buf_capacity) - ptr_err.ec = std::errc::value_too_large; - else - this->ptr_err.ptr = this->ptr_err.ptr + ret; + // this->buf_end minus 1 because we need space for adding the extra decimal point. + this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int); #endif + size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0); + if (writen_digits < digits) { + // Number is smaller than 10^digits, so that we will pad it with zeros. + size_t remaining_digits = digits - writen_digits; + // Move all newly inserted chars by remaining_digits to allocate space for padding with zeros. + for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits); + this->ptr_err.ptr += remaining_digits; + } + + // Move all newly inserted chars by one to allocate space for a decimal point. + for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + *(this->ptr_err.ptr - digits) = '.'; + for (size_t i = 0; i < digits; ++i) { + if (*this->ptr_err.ptr != '0') + break; + this->ptr_err.ptr--; + } + if (*this->ptr_err.ptr == '.') + this->ptr_err.ptr--; + if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-') + *(++this->ptr_err.ptr) = '0'; + this->ptr_err.ptr++; } void emit_xy(const Vec2d &point) { From e520454c3e1988c894399f138c863495505dec3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 9 Sep 2021 12:01:31 +0200 Subject: [PATCH 11/23] Fixed unit tests after the previous commit. --- t/combineinfill.t | 10 +++++++++- tests/fff_print/test_gcodewriter.cpp | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/t/combineinfill.t b/t/combineinfill.t index 925e5b11d4..a19e817a14 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -36,7 +36,15 @@ plan tests => 8; $layer_infill{$self->Z} = 1; } } - $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0; + # Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. + # We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail + # because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: + # "G1 Z5 F5000 ; lift nozzle" + # "G1 Z5.000 F7800.000" + # That has a different Z-axis position from the view of string comparisons of floating-point numbers. + # To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, + # we filtered out the G-code command with the commend 'lift nozzle'. + $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} && index($info->{comment}, 'lift nozzle') == -1; }); my $layers_with_perimeters = scalar(keys %layer_infill); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index a4c8aa09a7..15ffaebb48 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -78,13 +78,13 @@ SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") { } } WHEN("set_speed is called to set speed to 1") { - THEN("Output string is G1 F1.000") { - REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1.000\n")); + THEN("Output string is G1 F1") { + REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1\n")); } } WHEN("set_speed is called to set speed to 203.200022") { - THEN("Output string is G1 F203.200") { - REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.200\n")); + THEN("Output string is G1 F203.2") { + REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.2\n")); } } WHEN("set_speed is called to set speed to 203.200522") { From 4ac013ec9c82d376c762096d6bae31672ebdfc98 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 7 Sep 2021 15:15:22 +0200 Subject: [PATCH 12/23] Fixed painting gizmos with modifiers below the bed: - When a modifier was below the bed and all the object parts above, it would clip the modifier but not triangulate the cut. - When an object part was below, it would triangulate all modifiers with opaque orange color. Both should now be fixed. --- src/slic3r/GUI/3DScene.cpp | 11 ++++++++-- src/slic3r/GUI/3DScene.hpp | 5 +++++ src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 26 +++++++++++------------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 2d49a8bf38..ba72c432d8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -409,6 +409,11 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(color); } +void GLVolume::set_color(const std::array& rgba) +{ + color = rgba; +} + void GLVolume::set_render_color(float r, float g, float b, float a) { render_color = { r, g, b, a }; @@ -458,8 +463,9 @@ void GLVolume::set_render_color() render_color[3] = color[3]; } -void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) +std::array color_from_model_volume(const ModelVolume& model_volume) { + std::array color; if (model_volume.is_negative_volume()) { color[0] = 0.2f; color[1] = 0.2f; @@ -481,6 +487,7 @@ void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) color[2] = 1.0f; } color[3] = model_volume.is_model_part() ? 1.f : 0.5f; + return color; } Transform3d GLVolume::world_matrix() const @@ -635,7 +642,7 @@ int GLVolumeCollection::load_object_volume( color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); - v.set_color_from_model_volume(*model_volume); + v.set_color(color_from_model_volume(*model_volume)); #if ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.load_mesh(mesh, true); #else diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 35abe8bd4d..1a85cc41e1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -39,6 +39,10 @@ class ModelObject; class ModelVolume; enum ModelInstanceEPrintVolumeState : unsigned char; +// Return appropriate color based on the ModelVolume. +std::array color_from_model_volume(const ModelVolume& model_volume); + + // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { @@ -393,6 +397,7 @@ public: return out; } + void set_color(const std::array& rgba); void set_render_color(float r, float g, float b, float a); void set_render_color(const std::array& rgba); // Sets render color in dependence of current state diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d790e937c7..33399e38d9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1845,7 +1845,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re volume->extruder_id = extruder_id; volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color_from_model_volume(*mvs->model_volume); + volume->set_color(color_from_model_volume(*mvs->model_volume)); // updates volumes transformations volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 18ce9d73c4..b22e72be9e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -152,7 +152,7 @@ void InstancesHider::on_update() canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); canvas->set_use_clipping_planes(true); // Some objects may be sinking, do not show whatever is below the bed. - canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), 0.)); + canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); @@ -164,12 +164,7 @@ void InstancesHider::on_update() m_clippers.clear(); for (const TriangleMesh* mesh : meshes) { m_clippers.emplace_back(new MeshClipper); - if (mo->get_instance_min_z(active_inst) < SINKING_Z_THRESHOLD) - m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), 0.)); - else { - m_clippers.back()->set_plane(ClippingPlane::ClipsNothing()); - m_clippers.back()->set_limiting_plane(ClippingPlane::ClipsNothing()); - } + m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); m_clippers.back()->set_mesh(*mesh); } m_old_meshes = meshes; @@ -218,8 +213,16 @@ void InstancesHider::render_cut() const clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); glsafe(::glPushMatrix()); - glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + if (mv->is_model_part()) + glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + else { + const std::array& c = color_from_model_volume(*mv); + glsafe(::glColor4f(c[0], c[1], c[2], c[3])); + } + glsafe(::glPushAttrib(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_DEPTH_TEST)); clipper->render_cut(); + glsafe(::glPopAttrib()); glsafe(::glPopMatrix()); ++clipper_id; @@ -385,8 +388,6 @@ void ObjectClipper::on_update() m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); - //if (has_hollowed && m_clp_ratio != 0.) - // m_clp_ratio = 0.25; } } @@ -407,7 +408,6 @@ void ObjectClipper::render_cut() const const SelectionInfo* sel_info = get_pool()->selection_info(); const ModelObject* mo = sel_info->model_object(); Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - const bool sinking = mo->bounding_box().min.z() < SINKING_Z_THRESHOLD; size_t clipper_id = 0; for (const ModelVolume* mv : mo->volumes) { @@ -418,9 +418,7 @@ void ObjectClipper::render_cut() const auto& clipper = m_clippers[clipper_id]; clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - clipper->set_limiting_plane(sinking ? - ClippingPlane(Vec3d::UnitZ(), 0.) - : ClippingPlane::ClipsNothing()); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); glsafe(::glPushMatrix()); glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); From ae7d6db1d961f85b8e2788d707cef03de7e28fdb Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 10:04:16 +0200 Subject: [PATCH 13/23] Parallelization of the G-code export. Follow-up to 03b60486840f32e32dc54103dc3051f94e79b35a beee18f22991e369b1722a43bbcb692fa0d68af0 b5a007a683124ec3b01b61e892ed8eb39c034248 etc --- src/libslic3r/GCode.cpp | 132 ++++++++++++++++++++++++++++++++-------- src/libslic3r/GCode.hpp | 31 +++++++++- 2 files changed, 134 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2347225ab7..cebc9136f7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -35,6 +35,7 @@ #include "SVG.hpp" #include +#include #include @@ -1336,15 +1337,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Reset the cooling buffer internal state (the current position, feed rate, accelerations). m_cooling_buffer->reset(this->writer().get_position()); m_cooling_buffer->set_current_extruder(initial_extruder_id); - // Pair the object layers with the support layers by z, extrude them. - std::vector layers_to_print = collect_layers_to_print(object); - for (const LayerToPrint <p : layers_to_print) { - std::vector lrs; - lrs.emplace_back(std::move(ltp)); - this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), <p == &layers_to_print.back(), - nullptr, *print_object_instance_sequential_active - object.instances().data()); - print.throw_if_canceled(); - } + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file); #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); @@ -1401,14 +1397,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } print.throw_if_canceled(); } - // Extrude the layers. - for (auto &layer : layers_to_print) { - const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); - if (m_wipe_tower && layer_tools.has_wipe_tower) - m_wipe_tower->next_layer(); - this->process_layer(file, print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); - print.throw_if_canceled(); - } + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); @@ -1481,6 +1473,88 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } +// Process all layers of all objects (non-sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream) +{ + // The pipeline is variable: The vase mode filter is optional. + size_t layer_to_print_idx = 0; + const auto generator = tbb::make_filter(tbb::filter::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); + if (m_wipe_tower && layer_tools.has_wipe_tower) + m_wipe_tower->next_layer(); + print.throw_if_canceled(); + return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + } + }); + const auto spiral_vase = tbb::make_filter(tbb::filter::serial_in_order, + [&spiral_vase = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult { + spiral_vase.enable(in.spiral_vase_enable); + return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + }); + const auto cooling = tbb::make_filter(tbb::filter::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }); + const auto output = tbb::make_filter(tbb::filter::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + ); + + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output); + else + tbb::parallel_pipeline(12, generator & cooling & output); +} + +// Process all layers of a single object instance (sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream) +{ + // The pipeline is fixed: Neither wipe tower nor vase mode are implemented for sequential print. + size_t layer_to_print_idx = 0; + tbb::parallel_pipeline(12, + tbb::make_filter( + tbb::filter::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + print.throw_if_canceled(); + return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); + } + }) & + tbb::make_filter( + tbb::filter::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }) & + tbb::make_filter( + tbb::filter::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + )); +} + std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) { try { @@ -1890,9 +1964,7 @@ namespace Skirt { // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. -void GCode::process_layer( - // Write into the output file. - GCodeOutputStream &file, +GCode::LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, @@ -1908,11 +1980,6 @@ void GCode::process_layer( // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); - if (layer_tools.extruders.empty()) - // Nothing to extrude. - return; - - // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; for (const LayerToPrint &l : layers) { @@ -1922,6 +1989,12 @@ void GCode::process_layer( support_layer = l.support_layer; } const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + GCode::LayerResult result { {}, layer.id(), false, last_layer }; + if (layer_tools.extruders.empty()) + // Nothing to extrude. + return result; + + // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. coordf_t print_z = layer.print_z; bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); @@ -1943,7 +2016,7 @@ void GCode::process_layer( break; } } - m_spiral_vase->enable(enable); + result.spiral_vase_enable = enable; // If we're going to apply spiralvase to this layer, disable loop clipping. m_enable_loop_clipping = !enable; } @@ -2285,6 +2358,7 @@ void GCode::process_layer( } } +#if 0 // Apply spiral vase post-processing if this layer contains suitable geometry // (we must feed all the G-code into the post-processor, including the first // bottom non-spiral layers otherwise it will mess with positions) @@ -2308,8 +2382,14 @@ void GCode::process_layer( #endif /* HAS_PRESSURE_EQUALIZER */ file.write(gcode); +#endif + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); + + result.gcode = std::move(gcode); + result.cooling_buffer_flush = object_layer || last_layer; + return result; } void GCode::apply_print_config(const PrintConfig &print_config) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 1dbc5b70a0..93a11821e4 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -215,9 +215,16 @@ private: static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); - void process_layer( - // Write into the output file. - GCodeOutputStream &file, + + struct LayerResult { + std::string gcode; + size_t layer_id; + // Is spiral vase post processing enabled for this layer? + bool spiral_vase_enable { false }; + // Should the cooling buffer content be flushed at the end of this layer? + bool cooling_buffer_flush { false }; + }; + LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, @@ -228,6 +235,24 @@ private: // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream); + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } bool last_pos_defined() const { return m_last_pos_defined; } From 5a95794913a053611c5887ee7460d2da8ed75ac5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 13 Sep 2021 10:29:41 +0200 Subject: [PATCH 14/23] OSX specific: Improvements for wxMultiChoiceDialog: Height of a ChoiceListBox will respect to items count This improvement fixed #6926 - Checkbox columns in modal windows are stretched (macOS) --- src/slic3r/GUI/GUI_Factories.cpp | 41 ++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 92ba427b5b..4fec12d148 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -16,6 +16,10 @@ #include #include "slic3r/Utils/FixModelByWin10.hpp" +#ifdef __APPLE__ +#include "wx/dcclient.h" +#include "slic3r/Utils/MacDarkMode.hpp" +#endif namespace Slic3r { @@ -211,7 +215,6 @@ static int GetSelectedChoices( wxArrayInt& selections, const wxString& caption, const wxArrayString& choices) { -#ifdef _WIN32 wxMultiChoiceDialog dialog(nullptr, message, caption, choices); wxGetApp().UpdateDlgDarkUI(&dialog); @@ -219,6 +222,39 @@ static int GetSelectedChoices( wxArrayInt& selections, // deselects the first item which is selected by default dialog.SetSelections(selections); +#ifdef __APPLE__ + // Improvements for ChoiceListBox: Height of control will restect to items count + for (auto child : dialog.GetChildren()) + if (dynamic_cast(child) && !choices.IsEmpty()) { + wxClientDC dc(child); + + int height = dc.GetTextExtent(choices[0]).y; + int width = 0; + for (const auto& string : choices) + width = std::max(width, dc.GetTextExtent(string).x); + + // calculate best size of ListBox + height += 3 * mac_max_scaling_factor(); // extend height by margins + width += 3 * height; // extend width by checkbox width and margins + + // don't make the listbox too tall (limit height to around 10 items) + // but don't make it too small neither + int list_height = wxMax(height * wxMin(wxMax(choices.Count(), 3), 10), 70); + wxSize sz_best = wxSize(width, list_height); + + wxSize sz = child->GetSize(); + child->SetMinSize(sz_best); + + // extend Dialog size, if calculated best size of ListBox is bigger then its size + wxSize dlg_sz = dialog.GetSize(); + if (int delta_x = sz_best.x - sz.x; delta_x > 0) dlg_sz.x += delta_x; + if (int delta_y = sz_best.y - sz.y; delta_y > 0) dlg_sz.y += delta_y; + dialog.SetSize(dlg_sz); + + break; + } +#endif + if (dialog.ShowModal() != wxID_OK) { // NB: intentionally do not clear the selections array here, the caller @@ -229,9 +265,6 @@ static int GetSelectedChoices( wxArrayInt& selections, selections = dialog.GetSelections(); return static_cast(selections.GetCount()); -#else - return wxGetSelectedChoices(selections, message, caption, choices); -#endif } static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/) From 880feb3a3d7079fd1f7c6422e70094876084811d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 10:41:21 +0200 Subject: [PATCH 15/23] GCodeViewer slight opitmization: reuse (move) G-code line indices in G-code viewer. --- src/slic3r/GUI/GCodeViewer.cpp | 6 ++++-- src/slic3r/GUI/GCodeViewer.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 5 +---- src/slic3r/GUI/GLCanvas3D.hpp | 3 +-- src/slic3r/GUI/GUI_Preview.cpp | 3 +-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1b2375cf5e..c9e4bef7b7 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -284,7 +284,7 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } -void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector &lines_ends) +void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) { assert(! m_file.is_open()); if (m_file.is_open()) @@ -577,7 +577,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& // release gpu memory, if used reset(); - m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); + m_sequential_view.gcode_window.load_gcode(gcode_result.filename, + // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). + std::move(const_cast&>(gcode_result.lines_ends))); #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER if (wxGetApp().is_gcode_viewer()) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3509bfbe51..f566c80bd5 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -702,7 +702,7 @@ public: public: GCodeWindow() = default; ~GCodeWindow() { stop_mapping_file(); } - void load_gcode(const std::string& filename, const std::vector &lines_ends); + void load_gcode(const std::string& filename, std::vector &&lines_ends); void reset() { stop_mapping_file(); m_lines_ends.clear(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 33399e38d9..551760ee93 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2092,7 +2092,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old.finalize_geometry(gl_initialized); } -void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) +void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); @@ -2100,10 +2100,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) m_gcode_viewer.update_shells_color_by_extruder(m_config); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } -} -void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) -{ m_gcode_viewer.refresh(gcode_result, str_tool_colors); set_as_dirty(); request_extra_frame(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index eb0220cd47..704adb010c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -722,8 +722,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); - void load_gcode_preview(const GCodeProcessor::Result& gcode_result); - void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); + void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); void refresh_gcode_preview_render_paths(); void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index eb511177df..f8a10bb8e9 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -943,8 +943,7 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->set_selected_extruder(0); if (gcode_preview_data_valid) { // Load the real G-code preview. - m_canvas->load_gcode_preview(*m_gcode_result); - m_canvas->refresh_gcode_preview(*m_gcode_result, colors); + m_canvas->load_gcode_preview(*m_gcode_result, colors); m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); From fda8ef6fce84b1371c1737f8adb82ed937f45c39 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 13 Sep 2021 11:16:13 +0200 Subject: [PATCH 16/23] #5471 - Make the View controls 'sticky' --- src/slic3r/GUI/GCodeViewer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c9e4bef7b7..9b7d8ec47c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -563,6 +563,8 @@ GCodeViewer::GCodeViewer() set_toolpath_move_type_visible(EMoveType::Extrude, true); #endif // !ENABLE_SEAMS_USING_MODELS + m_extrusions.reset_role_visibility_flags(); + // m_sequential_view.skip_invisible_moves = true; } @@ -736,7 +738,6 @@ void GCodeViewer::reset() m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); m_filament_densities = std::vector(); - m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); m_shells.volumes.clear(); m_layers.reset(); From e7591e6aa60042c19a0aa20305af0f6e0d8439c0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 11:55:38 +0200 Subject: [PATCH 17/23] GCodeWriter: published GCodeFormatter, made it more generic, so it could be used outside of GCodeWriter. Ported the GCodeWriter::retract/deretract to GCodeFormatter. --- src/libslic3r/GCodeWriter.cpp | 223 ++++++++++++---------------------- src/libslic3r/GCodeWriter.hpp | 74 +++++++++++ 2 files changed, 150 insertions(+), 147 deletions(-) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index eb45e95c1f..83557fdbbf 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,7 +1,6 @@ #include "GCodeWriter.hpp" #include "CustomGCode.hpp" #include -#include #include #include #include @@ -11,15 +10,8 @@ #include #endif -#define XYZF_EXPORT_DIGITS 3 -#define E_EXPORT_DIGITS 5 - #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val -#define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; -#define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val) -#define XYZF_NUM(val) PRECISION(val, XYZF_EXPORT_DIGITS) -#define E_NUM(val) PRECISION(val, E_EXPORT_DIGITS) namespace Slic3r { @@ -261,115 +253,12 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) return gcode.str(); } -class G1Writer { -private: - static constexpr const size_t buflen = 256; - char buf[buflen]; - char *buf_end; - std::to_chars_result ptr_err; - -public: - G1Writer() { - this->buf[0] = 'G'; - this->buf[1] = '1'; - this->buf_end = this->buf + buflen; - this->ptr_err.ptr = this->buf + 2; - } - - void emit_axis(const char axis, const double v, size_t digits) { - assert(digits <= 6); - static constexpr const std::array pow_10{1, 10, 100, 1000, 10000, 100000, 1000000}; - *ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis; - - char *base_ptr = this->ptr_err.ptr; - auto v_int = int64_t(std::round(v * pow_10[digits])); - // Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it. - // That is a little bit slower than std::to_chars but not much. -#ifdef __APPLE__ - boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator(), v_int); -#else - // this->buf_end minus 1 because we need space for adding the extra decimal point. - this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int); -#endif - size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0); - if (writen_digits < digits) { - // Number is smaller than 10^digits, so that we will pad it with zeros. - size_t remaining_digits = digits - writen_digits; - // Move all newly inserted chars by remaining_digits to allocate space for padding with zeros. - for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr) - *to_ptr = *from_ptr; - - memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits); - this->ptr_err.ptr += remaining_digits; - } - - // Move all newly inserted chars by one to allocate space for a decimal point. - for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr) - *to_ptr = *from_ptr; - - *(this->ptr_err.ptr - digits) = '.'; - for (size_t i = 0; i < digits; ++i) { - if (*this->ptr_err.ptr != '0') - break; - this->ptr_err.ptr--; - } - if (*this->ptr_err.ptr == '.') - this->ptr_err.ptr--; - if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-') - *(++this->ptr_err.ptr) = '0'; - this->ptr_err.ptr++; - } - - void emit_xy(const Vec2d &point) { - this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); - this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); - } - - void emit_xyz(const Vec3d &point) { - this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); - this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); - this->emit_z(point.z()); - } - - void emit_z(const double z) { - this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); - } - - void emit_e(const std::string &axis, double v) { - if (! axis.empty()) { - // not gcfNoExtrusion - this->emit_axis(axis[0], v, E_EXPORT_DIGITS); - } - } - - void emit_f(double speed) { - this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); - } - - void emit_string(const std::string &s) { - strncpy(ptr_err.ptr, s.c_str(), s.size()); - ptr_err.ptr += s.size(); - } - - void emit_comment(bool allow_comments, const std::string &comment) { - if (allow_comments && ! comment.empty()) { - *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; - this->emit_string(comment); - } - } - - std::string string() { - *ptr_err.ptr ++ = '\n'; - return std::string(this->buf, ptr_err.ptr - buf); - } -}; - std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const { assert(F > 0.); assert(F < 100000.); - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_f(F); w.emit_comment(this->config.gcode_comments, comment); w.emit_string(cooling_marker); @@ -381,7 +270,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com m_pos(0) = point(0); m_pos(1) = point(1); - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_xy(point); w.emit_f(this->config.travel_speed.value * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -414,7 +303,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co m_lifted = 0; m_pos = point; - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_xyz(point); w.emit_f(this->config.travel_speed.value * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -448,7 +337,7 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) if (speed == 0.) speed = this->config.travel_speed.value; - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_z(z); w.emit_f(speed * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -473,7 +362,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: m_pos(1) = point(1); m_extruder->extrude(dE); - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_xy(point); w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_comment(this->config.gcode_comments, comment); @@ -486,7 +375,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std m_lifted = 0; m_extruder->extrude(dE); - G1Writer w; + auto w = GCodeFormatter::G1(); w.emit_xyz(point); w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_comment(this->config.gcode_comments, comment); @@ -517,12 +406,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe) std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment) { - std::ostringstream gcode; - /* If firmware retraction is enabled, we use a fake value of 1 since we ignore the actual configured retract_length which might be 0, in which case the retraction logic gets skipped. */ - if (this->config.use_firmware_retraction) length = 1; + if (this->config.use_firmware_retraction) + length = 1; // If we use volumetric E values we turn lengths into volumes */ if (this->config.use_volumetric_e) { @@ -532,52 +420,48 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std restart_extra = restart_extra * area; } - double dE = m_extruder->retract(length, restart_extra); - if (dE != 0) { + + std::string gcode; + if (double dE = m_extruder->retract(length, restart_extra); dE != 0) { if (this->config.use_firmware_retraction) { - if (FLAVOR_IS(gcfMachinekit)) - gcode << "G22 ; retract\n"; - else - gcode << "G10 ; retract\n"; + gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n"; } else if (! m_extrusion_axis.empty()) { - gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) - << " F" << XYZF_NUM(m_extruder->retract_speed() * 60.); - COMMENT(comment); - gcode << "\n"; + auto w = GCodeFormatter::G1(); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_f(m_extruder->retract_speed() * 60.); + w.emit_comment(this->config.gcode_comments, comment); + gcode = w.string(); } } if (FLAVOR_IS(gcfMakerWare)) - gcode << "M103 ; extruder off\n"; - - return gcode.str(); + gcode += "M103 ; extruder off\n"; + + return gcode; } std::string GCodeWriter::unretract() { - std::ostringstream gcode; + std::string gcode; if (FLAVOR_IS(gcfMakerWare)) - gcode << "M101 ; extruder on\n"; + gcode = "M101 ; extruder on\n"; - double dE = m_extruder->unretract(); - if (dE != 0) { + if (double dE = m_extruder->unretract(); dE != 0) { if (this->config.use_firmware_retraction) { - if (FLAVOR_IS(gcfMachinekit)) - gcode << "G23 ; unretract\n"; - else - gcode << "G11 ; unretract\n"; - gcode << this->reset_e(); + gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n"; + gcode += this->reset_e(); } else if (! m_extrusion_axis.empty()) { // use G1 instead of G0 because G0 will blend the restart with the previous travel move - gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) - << " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.); - if (this->config.gcode_comments) gcode << " ; unretract"; - gcode << "\n"; + auto w = GCodeFormatter::G1(); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_f(m_extruder->deretract_speed() * 60.); + w.emit_comment(this->config.gcode_comments, " ; unretract"); + gcode += w.string(); } } - return gcode.str(); + return gcode; } /* If this method is called more than once before calling unlift(), @@ -649,4 +533,49 @@ std::string GCodeWriter::set_fan(unsigned int speed) const return GCodeWriter::set_fan(this->config.gcode_flavor, this->config.gcode_comments, speed); } + +void GCodeFormatter::emit_axis(const char axis, const double v, size_t digits) { + assert(digits <= 6); + static constexpr const std::array pow_10{1, 10, 100, 1000, 10000, 100000, 1000000}; + *ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis; + + char *base_ptr = this->ptr_err.ptr; + auto v_int = int64_t(std::round(v * pow_10[digits])); + // Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it. + // That is a little bit slower than std::to_chars but not much. +#ifdef __APPLE__ + boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator(), v_int); +#else + // this->buf_end minus 1 because we need space for adding the extra decimal point. + this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int); +#endif + size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0); + if (writen_digits < digits) { + // Number is smaller than 10^digits, so that we will pad it with zeros. + size_t remaining_digits = digits - writen_digits; + // Move all newly inserted chars by remaining_digits to allocate space for padding with zeros. + for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits); + this->ptr_err.ptr += remaining_digits; + } + + // Move all newly inserted chars by one to allocate space for a decimal point. + for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + *(this->ptr_err.ptr - digits) = '.'; + for (size_t i = 0; i < digits; ++i) { + if (*this->ptr_err.ptr != '0') + break; + this->ptr_err.ptr--; + } + if (*this->ptr_err.ptr == '.') + this->ptr_err.ptr--; + if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-') + *(++this->ptr_err.ptr) = '0'; + this->ptr_err.ptr++; } + +} // namespace Slic3r diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index dd602ca804..12a2b5a888 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include +#include #include "Extruder.hpp" #include "Point.hpp" #include "PrintConfig.hpp" @@ -93,6 +94,79 @@ private: std::string _retract(double length, double restart_extra, const std::string &comment); }; +class GCodeFormatter { +private: + static constexpr const size_t buflen = 256; + char buf[buflen]; + char *buf_end; + std::to_chars_result ptr_err; + + GCodeFormatter() { + this->buf_end = buf + buflen; + this->ptr_err.ptr = this->buf; + } + +public: + static constexpr const int XYZF_EXPORT_DIGITS = 3; + static constexpr const int E_EXPORT_DIGITS = 5; + + static GCodeFormatter empty() { + return {}; + } + static GCodeFormatter G1() { + GCodeFormatter out; + out.buf[0] = 'G'; + out.buf[1] = '1'; + out.ptr_err.ptr += 2; + return out; + } + + void emit_axis(const char axis, const double v, size_t digits); + + void emit_xy(const Vec2d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + } + + void emit_xyz(const Vec3d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + this->emit_z(point.z()); + } + + void emit_z(const double z) { + this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); + } + + void emit_e(const std::string &axis, double v) { + if (! axis.empty()) { + // not gcfNoExtrusion + this->emit_axis(axis[0], v, E_EXPORT_DIGITS); + } + } + + void emit_f(double speed) { + this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); + } + + void emit_string(const std::string &s) { + strncpy(ptr_err.ptr, s.c_str(), s.size()); + ptr_err.ptr += s.size(); + } + + void emit_comment(bool allow_comments, const std::string &comment) { + if (allow_comments && ! comment.empty()) { + *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; + this->emit_string(comment); + } + } + + std::string string() { + *ptr_err.ptr ++ = '\n'; + return std::string(this->buf, ptr_err.ptr - buf); + } +}; + } /* namespace Slic3r */ #endif /* slic3r_GCodeWriter_hpp_ */ From e78d647cc239966fdbbd91b995b6f5bef3adc710 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 12:51:50 +0200 Subject: [PATCH 18/23] Follow-up to e7591e6aa60042c19a0aa20305af0f6e0d8439c0 GCodeFormatter default copy constructor / copy operators were not safe and they were used in debug mode. --- src/libslic3r/GCodeWriter.cpp | 16 ++++++------- src/libslic3r/GCodeWriter.hpp | 45 +++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 83557fdbbf..f57e254061 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -258,7 +258,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s assert(F > 0.); assert(F < 100000.); - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_f(F); w.emit_comment(this->config.gcode_comments, comment); w.emit_string(cooling_marker); @@ -270,7 +270,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com m_pos(0) = point(0); m_pos(1) = point(1); - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_xy(point); w.emit_f(this->config.travel_speed.value * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -303,7 +303,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co m_lifted = 0; m_pos = point; - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_xyz(point); w.emit_f(this->config.travel_speed.value * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -337,7 +337,7 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) if (speed == 0.) speed = this->config.travel_speed.value; - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_z(z); w.emit_f(speed * 60.0); w.emit_comment(this->config.gcode_comments, comment); @@ -362,7 +362,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: m_pos(1) = point(1); m_extruder->extrude(dE); - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_xy(point); w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_comment(this->config.gcode_comments, comment); @@ -375,7 +375,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std m_lifted = 0; m_extruder->extrude(dE); - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_xyz(point); w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_comment(this->config.gcode_comments, comment); @@ -426,7 +426,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std if (this->config.use_firmware_retraction) { gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n"; } else if (! m_extrusion_axis.empty()) { - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_f(m_extruder->retract_speed() * 60.); w.emit_comment(this->config.gcode_comments, comment); @@ -453,7 +453,7 @@ std::string GCodeWriter::unretract() gcode += this->reset_e(); } else if (! m_extrusion_axis.empty()) { // use G1 instead of G0 because G0 will blend the restart with the previous travel move - auto w = GCodeFormatter::G1(); + GCodeG1Formatter w; w.emit_e(m_extrusion_axis, m_extruder->E()); w.emit_f(m_extruder->deretract_speed() * 60.); w.emit_comment(this->config.gcode_comments, " ; unretract"); diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 12a2b5a888..6e2c08d3b5 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -95,32 +95,18 @@ private: }; class GCodeFormatter { -private: - static constexpr const size_t buflen = 256; - char buf[buflen]; - char *buf_end; - std::to_chars_result ptr_err; - - GCodeFormatter() { - this->buf_end = buf + buflen; +public: + GCodeFormatter() { + this->buf_end = buf + buflen; this->ptr_err.ptr = this->buf; } -public: + GCodeFormatter(const GCodeFormatter&) = delete; + GCodeFormatter& operator=(const GCodeFormatter&) = delete; + static constexpr const int XYZF_EXPORT_DIGITS = 3; static constexpr const int E_EXPORT_DIGITS = 5; - static GCodeFormatter empty() { - return {}; - } - static GCodeFormatter G1() { - GCodeFormatter out; - out.buf[0] = 'G'; - out.buf[1] = '1'; - out.ptr_err.ptr += 2; - return out; - } - void emit_axis(const char axis, const double v, size_t digits); void emit_xy(const Vec2d &point) { @@ -165,6 +151,25 @@ public: *ptr_err.ptr ++ = '\n'; return std::string(this->buf, ptr_err.ptr - buf); } + +protected: + static constexpr const size_t buflen = 256; + char buf[buflen]; + char* buf_end; + std::to_chars_result ptr_err; +}; + +class GCodeG1Formatter : public GCodeFormatter { +public: + GCodeG1Formatter() { + this->buf[0] = 'G'; + this->buf[1] = '1'; + this->buf_end = buf + buflen; + this->ptr_err.ptr = this->buf + 2; + } + + GCodeG1Formatter(const GCodeG1Formatter&) = delete; + GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete; }; } /* namespace Slic3r */ From f9a5ee725d71898179b2ec9e22844b977b4f98b6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 13:04:12 +0200 Subject: [PATCH 19/23] Follow-up to ae7d6db1d961f85b8e2788d707cef03de7e28fdb Exporting G-code on a worker thread did not work correctly as the worker threads were using user's locale, not "C" locale. The "C" locale is newly enforced to TBB worker threads by name_tbb_thread_pool_threads_set_locale() --- src/libslic3r/Print.cpp | 2 +- src/libslic3r/SLAPrint.cpp | 2 +- src/libslic3r/Thread.cpp | 18 +++++++++++++++++- src/libslic3r/Thread.hpp | 3 ++- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 4 ++-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 06052a62f3..27dca82431 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -812,7 +812,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const // Slicing process, running at a background thread. void Print::process() { - name_tbb_thread_pool_threads(); + name_tbb_thread_pool_threads_set_locale(); BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 0b9dde1228..61ff908d3b 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -693,7 +693,7 @@ void SLAPrint::process() if (m_objects.empty()) return; - name_tbb_thread_pool_threads(); + name_tbb_thread_pool_threads_set_locale(); // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 25c29d2735..106da4a781 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -188,7 +188,8 @@ std::optional get_current_thread_name() #endif // _WIN32 // Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID. -void name_tbb_thread_pool_threads() +// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator. +void name_tbb_thread_pool_threads_set_locale() { static bool initialized = false; if (initialized) @@ -233,6 +234,21 @@ void name_tbb_thread_pool_threads() std::ostringstream name; name << "slic3r_tbb_" << range.begin(); set_current_thread_name(name.str().c_str()); + // Set locales of the worker thread to "C". +#ifdef _WIN32 + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + std::setlocale(LC_ALL, "C"); +#else + // We are leaking some memory here, because the newlocale() produced memory will never be released. + // This is not a problem though, as there will be a maximum one worker thread created per physical thread. + uselocale(newlocale( +#ifdef __APPLE__ + LC_ALL_MASK +#else // some Unix / Linux / BSD + LC_ALL +#endif + , "C", nullptr)); +#endif } }); } diff --git a/src/libslic3r/Thread.hpp b/src/libslic3r/Thread.hpp index a861237962..9afe13b42d 100644 --- a/src/libslic3r/Thread.hpp +++ b/src/libslic3r/Thread.hpp @@ -33,7 +33,8 @@ std::optional get_current_thread_name(); // To be called somewhere before the TBB threads are spinned for the first time, to // give them names recognizible in the debugger. -void name_tbb_thread_pool_threads(); +// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator. +void name_tbb_thread_pool_threads_set_locale(); template inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index b4de4a509f..e93f32b03e 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -209,8 +209,8 @@ void BackgroundSlicingProcess::process_sla() void BackgroundSlicingProcess::thread_proc() { - set_current_thread_name("slic3r_BgSlcPcs"); - name_tbb_thread_pool_threads(); + set_current_thread_name("slic3r_BgSlcPcs"); + name_tbb_thread_pool_threads_set_locale(); assert(m_print != nullptr); assert(m_print == m_fff_print || m_print == m_sla_print); From 60b5e0d0d5ead48d86bbdc78d48ede156d50a5e4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 13 Sep 2021 13:34:49 +0200 Subject: [PATCH 20/23] Fixed wrong layout of preview's combos popup windows the first time they are open --- src/slic3r/GUI/GUI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 59ccd9d66d..00490d03da 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -383,6 +383,7 @@ void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, cons // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 // comboCtrl->EnablePopupAnimation(false); + popup->SetFont(comboCtrl->GetFont()); comboCtrl->SetPopupControl(popup); wxString title = from_u8(text); max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x); From cab71073a1864e05582a480945bfb8d224219bdd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 15:13:05 +0200 Subject: [PATCH 21/23] Some reduction of unnecessary conversions when calling ClipperUtils. --- src/libslic3r/ClipperUtils.cpp | 12 +++++++++++- src/libslic3r/ClipperUtils.hpp | 5 +++++ src/libslic3r/ExPolygon.cpp | 2 +- src/libslic3r/Fill/FillAdaptive.cpp | 2 +- src/libslic3r/Polygon.hpp | 8 +++----- src/libslic3r/Polyline.cpp | 9 +-------- src/libslic3r/Polyline.hpp | 7 +++---- src/libslic3r/Print.cpp | 2 +- src/libslic3r/SLA/Pad.cpp | 2 +- src/libslic3r/SupportMaterial.cpp | 2 +- src/slic3r/GUI/2DBed.cpp | 2 +- 11 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 8a4dfc03cf..6db624590d 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -467,6 +467,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) @@ -496,6 +498,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfac { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -610,12 +614,18 @@ Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subj Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip) { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) @@ -637,7 +647,7 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol // convert Polylines to Lines Lines retval; for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - retval.emplace_back(polyline->operator Line()); + retval.emplace_back(polyline->line()); return retval; } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index c4e014a740..f49d922c14 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -303,6 +303,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygo Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); @@ -312,6 +313,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surf Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); @@ -322,6 +324,7 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon } // Safety offset is applied to the clipping polygons only. +Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); @@ -337,6 +340,8 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 506ba8cb6f..4a40c02c30 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -92,7 +92,7 @@ bool ExPolygon::contains(const Line &line) const bool ExPolygon::contains(const Polyline &polyline) const { - return diff_pl((Polylines)polyline, *this).empty(); + return diff_pl(polyline, *this).empty(); } bool ExPolygon::contains(const Polylines &polylines) const diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 3eabc6106c..91610cf511 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -928,7 +928,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; assert(line_alg::distance_to_squared(l, Vec2d(pt.cast())) > 1000 * 1000); #endif // NDEBUG - } else if (((Line)pl).distance_to_squared(pt) <= 1000 * 1000) + } else if (pl.line().distance_to_squared(pt) <= 1000 * 1000) out = closest.front().second; } return out; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 333f1e6b1a..3c46a564b2 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -18,11 +18,6 @@ using ConstPolygonPtrs = std::vector; class Polygon : public MultiPoint { public: - explicit operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } - explicit operator Polyline() const { return this->split_at_first_point(); } - Point& operator[](Points::size_type idx) { return this->points[idx]; } - const Point& operator[](Points::size_type idx) const { return this->points[idx]; } - Polygon() = default; virtual ~Polygon() = default; explicit Polygon(const Points &points) : MultiPoint(points) {} @@ -39,6 +34,9 @@ public: Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } + Point& operator[](Points::size_type idx) { return this->points[idx]; } + const Point& operator[](Points::size_type idx) const { return this->points[idx]; } + // last point == first point for polygons const Point& last_point() const override { return this->points.front(); } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 68c40fc204..e451e04c9d 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -10,14 +10,7 @@ namespace Slic3r { -Polyline::operator Polylines() const -{ - Polylines polylines; - polylines.push_back(*this); - return polylines; -} - -Polyline::operator Line() const +Line Polyline::line() const { if (this->points.size() > 2) throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 758fc38cd5..cc090b0843 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -59,13 +59,12 @@ public: src.points.clear(); } } - - explicit operator Polylines() const; - explicit operator Line() const; + const Point& last_point() const override { return this->points.back(); } - const Point& leftmost_point() const; + Line line() const; Lines lines() const override; + void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 27dca82431..c673f8810e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -397,7 +397,7 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly convex_hull.translate(instance.shift - print_object->center_offset()); // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls for (size_t i = 0; i < convex_hulls_other.size(); ++i) { - if (!intersection((Polygons)convex_hulls_other[i], (Polygons)convex_hull).empty()) { + if (! intersection(convex_hulls_other[i], convex_hull).empty()) { if (polygons == nullptr) return false; else { diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index 8d995b59e3..1e5de51589 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -370,7 +370,7 @@ bool add_cavity(indexed_triangle_set &pad, if (inner_base.empty() || middle_base.empty()) { logerr(); return false; } - ExPolygons pdiff = diff_ex((Polygons)top_poly, (Polygons)middle_base.contour); + ExPolygons pdiff = diff_ex(top_poly, middle_base.contour); if (pdiff.size() != 1) { logerr(); return false; } diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 1b66bcc533..f727d334d9 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3338,7 +3338,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; const Point *seg_current_pt = nullptr; coordf_t seg_current_t = 0.; - if (! intersection_pl((Polylines)contour.split_at_first_point(), overhang_with_margin).empty()) { + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { // The contour is below the overhang at least to some extent. //FIXME ideally one would place the circles below the overhang only. // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index e184b43fc0..e7c2cbe542 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -92,7 +92,7 @@ void Bed_2D::repaint(const std::vector& shape) for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) { polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) })); } - polylines = intersection_pl(polylines, (Polygons)bed_polygon); + polylines = intersection_pl(polylines, bed_polygon); dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID)); for (auto pl : polylines) From 0a51afa3e64d5367023075f6dcadd81f801bdd25 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 15:40:56 +0200 Subject: [PATCH 22/23] Fix of Can't convert polyline with more than two points to a line (#6933) Sometimes Clipper produces a polyline with more than 2 points when clipping a line with a polygon or a set of polygons. We hope the intermediate points are collinear with the line, so we may just ignore them. --- src/libslic3r/ClipperUtils.cpp | 4 +++- src/libslic3r/Fill/FillAdaptive.cpp | 4 +++- src/libslic3r/MultiPoint.hpp | 4 +++- src/libslic3r/Polyline.cpp | 7 ------- src/libslic3r/Polyline.hpp | 1 - 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 6db624590d..a1dd638cae 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -647,7 +647,9 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol // convert Polylines to Lines Lines retval; for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - retval.emplace_back(polyline->line()); + if (polyline->size() >= 2) + //FIXME It may happen, that Clipper produced a polyline with more than 2 collinear points by clipping a single line with polygons. It is a very rare issue, but it happens, see GH #6933. + retval.push_back({ polyline->front(), polyline->back() }); return retval; } diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 91610cf511..29b343db01 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -928,7 +928,9 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; assert(line_alg::distance_to_squared(l, Vec2d(pt.cast())) > 1000 * 1000); #endif // NDEBUG - } else if (pl.line().distance_to_squared(pt) <= 1000 * 1000) + } else if (pl.size() >= 2 && + //FIXME Hoping that pl is really a line, trimmed by a polygon using ClipperUtils. Sometimes Clipper leaves some additional collinear points on the polyline, let's hope it is all right. + Line{ pl.front(), pl.back() }.distance_to_squared(pt) <= 1000 * 1000) out = closest.front().second; } return out; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 46b47832a7..935348279c 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -33,7 +33,9 @@ public: void rotate(double angle, const Point ¢er); void reverse() { std::reverse(this->points.begin(), this->points.end()); } - const Point& first_point() const { return this->points.front(); } + const Point& front() const { return this->points.front(); } + const Point& back() const { return this->points.back(); } + const Point& first_point() const { return this->front(); } virtual const Point& last_point() const = 0; virtual Lines lines() const = 0; size_t size() const { return points.size(); } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index e451e04c9d..1255d5473d 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -10,13 +10,6 @@ namespace Slic3r { -Line Polyline::line() const -{ - if (this->points.size() > 2) - throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); - return Line(this->points.front(), this->points.back()); -} - const Point& Polyline::leftmost_point() const { const Point *p = &this->points.front(); diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index cc090b0843..31e0b88d32 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -62,7 +62,6 @@ public: const Point& last_point() const override { return this->points.back(); } const Point& leftmost_point() const; - Line line() const; Lines lines() const override; void clip_end(double distance); From ec976cbe0539c8dbb6c3adcd0f6fe15a1b389492 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Sep 2021 15:45:33 +0200 Subject: [PATCH 23/23] Fixed unit tests after cab71073a1864e05582a480945bfb8d224219bdd --- tests/libslic3r/test_clipper_utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index bbf76ea180..3cbd21a40b 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -99,7 +99,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { { 74730000, 74730000 }, { 55270000, 74730000 }, { 55270000, 68063296 }, { 44730000, 68063296 }, { 44730000, 74730000 }, { 25270000, 74730000 }, { 25270000, 55270000 }, { 31936670, 55270000 }, { 31936670, 44730000 }, { 25270000, 44730000 }, { 25270000, 25270000 }, { 44730000, 25270000 }, { 44730000, 31936670 } }; Slic3r::Polygon clip { {75200000, 45200000}, {54800000, 45200000}, {54800000, 24800000}, {75200000, 24800000} }; - Slic3r::Polylines result = Slic3r::intersection_pl({ subject }, { clip }); + Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip }); THEN("intersection_pl - result is not empty") { REQUIRE(result.size() == 1); } @@ -117,7 +117,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { GIVEN("Clipper bug #126") { Slic3r::Polyline subject { { 200000, 19799999 }, { 200000, 200000 }, { 24304692, 200000 }, { 15102879, 17506106 }, { 13883200, 19799999 }, { 200000, 19799999 } }; Slic3r::Polygon clip { { 15257205, 18493894 }, { 14350057, 20200000 }, { -200000, 20200000 }, { -200000, -200000 }, { 25196917, -200000 } }; - Slic3r::Polylines result = Slic3r::intersection_pl({ subject }, { clip }); + Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip }); THEN("intersection_pl - result is not empty") { REQUIRE(result.size() == 1); }