diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 69a9e87f9..a13d578a3 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -50,7 +50,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr { ExPolygons ex_polygons; for (LayerRegion *region : print_object.layers().front()->regions()) - Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON))); + Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON))); return ex_polygons; } diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 9b95bfed6..28fb09313 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -452,6 +452,11 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const fl { return PolyTreeToExPolygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(offset_paths(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // Offset outside, then inside produces morphological closing. All deltas should be positive. Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) @@ -466,6 +471,13 @@ Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delt assert(delta2 > 0); return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // Offset inside, then outside produces morphological opening. All deltas should be positive. Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) @@ -474,6 +486,19 @@ Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, c assert(delta2 > 0); return to_polygons(expand_paths(shrink_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::ExPolygonsProvider(expolygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return to_polygons(expand_paths(shrink_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // Fix of #117: A large fractal pyramid takes ages to slice // The Clipper library has difficulties processing overlapping polygons. @@ -525,6 +550,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 diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(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) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 829611176..bbd91c0fd 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -345,19 +345,35 @@ inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const floa // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); // Offset outside, then inside produces morphological closing. All deltas should be positive. Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); -inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return closing(polygons, delta, delta, joinType, miterLimit); } Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); -inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing_ex(polygons, delta, delta, joinType, miterLimit); } -inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return closing_ex(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); } // Offset inside, then outside produces morphological opening. All deltas should be positive. // Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons. Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); -inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return opening(polygons, delta, delta, joinType, miterLimit); } -inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } +Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(polygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(expolygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(surfaces, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); } Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); @@ -366,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); 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); diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 9ca85c728..3dcc121c1 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -35,6 +35,7 @@ void CoolingBuffer::reset(const Vec3d &position) m_current_pos[1] = float(position.y()); m_current_pos[2] = float(position.z()); m_current_pos[4] = float(m_config.travel_speed.value); + m_fan_speed = -1; } struct CoolingLine @@ -689,10 +690,9 @@ std::string CoolingBuffer::apply_layer_cooldown( // Second generate the adjusted G-code. std::string new_gcode; new_gcode.reserve(gcode.size() * 2); - int fan_speed = -1; 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 ]() { + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() { #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; @@ -733,9 +733,9 @@ std::string CoolingBuffer::apply_layer_cooldown( bridge_fan_speed = 0; fan_speed_new = 0; } - if (fan_speed_new != fan_speed) { - fan_speed = fan_speed_new; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + if (fan_speed_new != m_fan_speed) { + m_fan_speed = fan_speed_new; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } }; @@ -759,7 +759,7 @@ std::string CoolingBuffer::apply_layer_cooldown( 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 += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_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 5f49ef455..1fe040518 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -41,6 +41,8 @@ private: // X,Y,Z,E,F std::vector m_axis; std::vector m_current_pos; + // Current known fan speed or -1 if not known yet. + int m_fan_speed; // Cached from GCodeWriter. // Printing extruder IDs, zero based. std::vector m_extruder_ids; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 7b106463a..aa04e69f2 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine auto it_end = it; for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) if (*it_end == '\n') - line_end_callback((it_end - buffer.begin()) + 1); + line_end_callback(file_pos + (it_end - buffer.begin()) + 1); // End of line is indicated also if end of file was reached. eol |= eof && it_end == it_bufend; if (eol) { @@ -173,7 +173,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine if (it != it_bufend && *it == '\r') ++ it; if (it != it_bufend && *it == '\n') { - line_end_callback((it - buffer.begin()) + 1); + line_end_callback(file_pos + (it - buffer.begin()) + 1); ++ it; } } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 356811b74..4dbffe7b0 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces)); - Polygons tmp = intersection(surfaces, trimming_polygons); - append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); + append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); this->slices.set(union_ex(tmp), stInternal); } diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index e2e5e254a..95daa33a6 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -159,8 +159,9 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) int i = 0; Vec3i facet; for (auto v : vtc) { - if (i > 2 || v < 0 || v >= cgalmesh.vertices().size()) { i = 0; break; } - facet(i++) = v; + int iv = v; + if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; } + facet(i++) = iv; } if (i == 3) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dd5a2b573..fd98feff7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells() // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); @@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 106da4a78..4e7bd073a 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale() nthreads = 1; #endif - std::atomic nthreads_running(0); + size_t nthreads_running(0); std::condition_variable cv; std::mutex cv_m; auto master_thread_id = std::this_thread::get_id(); @@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale() tbb::blocked_range(0, nthreads, 1), [&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range &range) { assert(range.begin() + 1 == range.end()); - if (nthreads_running.fetch_add(1) + 1 == nthreads) { + if (std::unique_lock lk(cv_m); ++nthreads_running == nthreads) { + lk.unlock(); // All threads are spinning. // Wake them up. cv.notify_all(); } else { // Wait for the last thread to wake the others. - std::unique_lock lk(cv_m); cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;}); } auto thread_id = std::this_thread::get_id(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a98998a84..7d6940bd0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -676,16 +676,18 @@ void GUI_App::post_init() if (this->preset_updater) { this->check_updates(false); CallAfter([this] { - this->config_wizard_startup(); + bool cw_showed = this->config_wizard_startup(); this->preset_updater->slic3r_update_notify(); this->preset_updater->sync(preset_bundle); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } }); } - // 'Send system info' dialog. Again, a CallAfter is needed on mac. - // Without it, GL extensions did not show. - CallAfter([] { show_send_system_info_dialog_if_needed(); }); - #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 2a27ce74a..40f5c3116 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -215,7 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { - ImGui::SetNextWindowFocus(); + // Uncomment if imgui window focus is needed on hover. I cant find any case. + //ImGui::SetNextWindowFocus(); set_hovered(); } diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ee80131e0..3748e2251 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -452,8 +452,10 @@ void PreferencesDialog::build(size_t selected_tab) activate_options_tab(m_optgroup_gui); // set Field for notify_release to its value to activate the object - boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); - m_optgroup_gui->get_field("notify_release")->set_value(val, false); + if (is_editor) { + boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); + m_optgroup_gui->get_field("notify_release")->set_value(val, false); + } if (is_editor) { create_icon_size_slider(); diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index db6ebad57..106617e76 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -17,6 +18,7 @@ #include #include #include +#include #include #include @@ -36,12 +38,17 @@ #include #pragma comment(lib, "iphlpapi.lib") #elif __APPLE__ -#import + #import + #include +#else // Linux/BSD + #include #endif namespace Slic3r { namespace GUI { +static const std::string SEND_SYSTEM_INFO_DOMAIN = "prusa3d.com"; +static const std::string SEND_SYSTEM_INFO_URL = "https://files." + SEND_SYSTEM_INFO_DOMAIN + "/wp-json/v1/ps"; // Declaration of a free function defined in OpenGLManager.cpp: @@ -52,8 +59,8 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value); class SendSystemInfoDialog : public DPIDialog { enum { - MIN_WIDTH = 80, - MIN_HEIGHT = 50 + MIN_WIDTH = 70, + MIN_HEIGHT = 34 }; public: @@ -129,20 +136,36 @@ public: // current version is newer. Only major and minor versions are compared. static bool should_dialog_be_shown() { - return false; - std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent"); Semver semver_current(SLIC3R_VERSION); Semver semver_last_sent; if (! last_sent_version.empty()) semver_last_sent = Semver(last_sent_version); - if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") - return false; // Don't show in alphas. + // set whether to show in alpha builds, or only betas/rcs/finals: + const bool show_in_alphas = true; - // Show the dialog if current > last, but they differ in more than just patch. - return ((semver_current.maj() > semver_last_sent.maj()) + if (! show_in_alphas && semver_current.prerelease() + && std::string(semver_current.prerelease()).find("alpha") != std::string::npos) + return false; + + // New version means current > last, but they must differ in more than just patch. + bool new_version = ((semver_current.maj() > semver_last_sent.maj()) || (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() )); + + if (! new_version) + return false; + + // We'll misuse the version check to check internet connection here. + bool is_internet = false; + Http::get(wxGetApp().app_config->version_check_url()) + .size_limit(SLIC3R_VERSION_BODY_MAX) + .timeout_max(2) + .on_complete([&](std::string, unsigned) { + is_internet = true; + }) + .perform_sync(); + return is_internet; } @@ -162,9 +185,10 @@ static std::map get_cpu_info_from_registry() std::map out; int idx = -1; - constexpr DWORD bufsize_ = 200; - DWORD bufsize = bufsize_; + constexpr DWORD bufsize_ = 500; + DWORD bufsize = bufsize_-1; // Ensure a terminating zero. char buf[bufsize_] = ""; + memset(buf, 0, bufsize_); const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\"; std::string reg_path = reg_dir; @@ -186,7 +210,7 @@ static std::map get_cpu_info_from_registry() } ++idx; reg_path = reg_dir + std::to_string(idx) + "\\"; - bufsize = bufsize_; + bufsize = bufsize_-1; } return out; } @@ -194,7 +218,7 @@ static std::map get_cpu_info_from_registry() static std::map parse_lscpu_etc(const std::string& name, char delimiter) { std::map out; - constexpr size_t max_len = 100; + constexpr size_t max_len = 1000; char cline[max_len] = ""; FILE* fp = popen(name.data(), "r"); if (fp != NULL) { @@ -260,10 +284,12 @@ static std::string get_unique_id() char buf[buf_size] = ""; memset(&buf, 0, sizeof(buf)); io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); - CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); - IOObjectRelease(ioRegistryRoot); - CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); - CFRelease(uuidCf); + if (ioRegistryRoot != MACH_PORT_NULL) { + CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); + CFRelease(uuidCf); + } // Now convert the string to std::vector. for (char* c = buf; *c != 0; ++c) unique.emplace_back((unsigned char)(*c)); @@ -318,11 +344,20 @@ static std::string generate_system_info_json() std::string unique_id = get_unique_id(); // Get system language. - std::string sys_language = "Unknown"; - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) - sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); - + std::string sys_language = "Unknown"; // important to init, see the __APPLE__ block. + #ifndef __APPLE__ + // Following apparently does not work on macOS. + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) + sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); + #else // __APPLE__ + CFLocaleRef cflocale = CFLocaleCopyCurrent(); + CFStringRef value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleLanguageCode); + char temp[10] = ""; + CFStringGetCString(value, temp, 10, kCFStringEncodingUTF8); + sys_language = temp; + CFRelease(cflocale); + #endif // Build a property tree with all the information. namespace pt = boost::property_tree; @@ -364,9 +399,13 @@ static std::string generate_system_info_json() data_node.put("SystemLanguage", sys_language); data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language")); + pt::ptree hw_node; - hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); - hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000)); + { + hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); + size_t num = std::round(Slic3r::total_physical_memory()/107374100.); + hw_node.put("RAM_GiB", std::to_string(num / 10) + "." + std::to_string(num % 10)); + } // Now get some CPU info: pt::ptree cpu_node; @@ -381,31 +420,32 @@ static std::string generate_system_info_json() cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]); cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]); #else // linux/BSD - std::map lscpu = parse_lscpu_etc("lscpu", ':'); - cpu_node.put("Arch", lscpu["Architecture"]); - cpu_node.put("Cores", lscpu["CPU(s)"]); - cpu_node.put("Model", lscpu["Model name"]); - cpu_node.put("Vendor", lscpu["Vendor ID"]); + std::map lscpu = parse_lscpu_etc("cat /proc/cpuinfo", ':'); + if (auto ncpu_it = lscpu.find("processor"); ncpu_it != lscpu.end()) { + std::string& ncpu = ncpu_it->second; + if (int num=0; std::from_chars(ncpu.data(), ncpu.data() + ncpu.size(), num).ec != std::errc::invalid_argument) + ncpu = std::to_string(num + 1); + } + cpu_node.put("Cores", lscpu["processor"]); + cpu_node.put("Model", lscpu["model name"]); + cpu_node.put("Vendor", lscpu["vendor_id"]); #endif hw_node.add_child("CPU", cpu_node); pt::ptree monitors_node; for (int i=0; i remove_whitespace; + remove_whitespace = [&remove_whitespace](pt::ptree& t) -> void + { + if (t.empty()) // Trim whitespace + boost::algorithm::trim(t.data()); + else + for (auto it = t.begin(); it != t.end(); ++it) + remove_whitespace(it->second); + }; + remove_whitespace(root); + // Serialize the tree into JSON and return it. std::stringstream ss; pt::write_json(ss, root); return ss.str(); - - // FURTHER THINGS TO CONSIDER: - //std::cout << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << std::endl; // Unix - // ? CPU, GPU, UNKNOWN ? - // printers? will they be installed already? } @@ -453,6 +501,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { + const int em = GUI::wxGetApp().em_unit(); + // Get current PrusaSliver version info. std::string app_name; { @@ -500,7 +550,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) std::string("") + filename + ""); wxString label3 = _L("Show verbatim data that will be sent"); - auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(70*em, 34*em), wxHW_SCROLLBAR_NEVER); wxString html = GUI::format_wxstr( "" "
" @@ -514,7 +564,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) + "" + label3 + "
" + "", bgr_clr_str, text_clr_str); html_window->SetPage(html); - html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent &evt) { + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent&) { ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7)); dlg.ShowModal(); }); @@ -526,7 +576,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info")); auto* hsizer = new wxBoxSizer(wxHORIZONTAL); - const int em = GUI::wxGetApp().em_unit(); hsizer->Add(m_btn_ask_later); hsizer->AddSpacer(em); hsizer->Add(m_btn_dont_send); @@ -548,6 +597,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), std::max(size.GetHeight(), MIN_HEIGHT * em)); + CenterOnParent(); + m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) { if (send_info()) { @@ -592,15 +643,16 @@ bool SendSystemInfoDialog::send_info() } result; // No synchronization needed, UI thread reads only after worker is joined. auto send = [&job_done, &result](const std::string& data) { - const std::string url = "https://files.prusa3d.com/wp-json/v1/ps"; - Http http = Http::post(url); + Http http = Http::post(SEND_SYSTEM_INFO_URL); http.header("Content-Type", "application/json") + .timeout_max(6) // seconds .set_post_body(data) .on_complete([&result](std::string body, unsigned status) { result = { Result::Success, _L("System info sent successfully. Thank you.") }; }) .on_error([&result](std::string body, std::string error, unsigned status) { - result = { Result::Error, GUI::format_wxstr(_L("Sending system info failed! Status: %1%"), status) }; + result = { Result::Error, _L("Sending system info failed!") }; + BOOST_LOG_TRIVIAL(error) << "Sending system info failed! STATUS: " << status; }) .on_progress([&job_done, &result](Http::Progress, bool &cancel) { if (job_done) // UI thread wants us to cancel. @@ -622,8 +674,10 @@ bool SendSystemInfoDialog::send_info() job_done = true; // In case the user closed the dialog, let the other thread know sending_thread.join(); // and wait until it terminates. - InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); - info_dlg.ShowModal(); + if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him. + InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); + info_dlg.ShowModal(); + } return result.value == Result::Success; } diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index f1614017f..fc7afbb34 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -104,6 +104,7 @@ struct Http::priv { enum { DEFAULT_TIMEOUT_CONNECT = 10, + DEFAULT_TIMEOUT_MAX = 0, DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, }; @@ -137,6 +138,7 @@ struct Http::priv static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); void set_timeout_connect(long timeout); + void set_timeout_max(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); void set_post_body(const std::string &body); @@ -163,6 +165,7 @@ Http::priv::priv(const std::string &url) } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); + set_timeout_max(DEFAULT_TIMEOUT_MAX); ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION); ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); @@ -253,6 +256,11 @@ void Http::priv::set_timeout_connect(long timeout) ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); } +void Http::priv::set_timeout_max(long timeout) +{ + ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); +} + void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) { // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows @@ -409,6 +417,13 @@ Http& Http::timeout_connect(long timeout) return *this; } +Http& Http::timeout_max(long timeout) +{ + if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; } + if (p) { p->set_timeout_max(timeout); } + return *this; +} + Http& Http::size_limit(size_t sizeLimit) { if (p) { p->limit = sizeLimit; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 52e48a394..61d84c51e 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -58,6 +58,8 @@ public: // Sets a maximum connection timeout in seconds Http& timeout_connect(long timeout); + // Sets a maximum total request timeout in seconds + Http& timeout_max(long timeout); // Sets a maximum size of the data that can be received. // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 13c631c9c..76ecc76d9 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -46,10 +46,6 @@ using Slic3r::GUI::Config::SnapshotDB; namespace Slic3r { -enum { - SLIC3R_VERSION_BODY_MAX = 256, -}; - static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index b7937c574..d0d18a7d8 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -13,6 +13,8 @@ class AppConfig; class PresetBundle; class Semver; +const int SLIC3R_VERSION_BODY_MAX = 256; + class PresetUpdater { public: