diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d373b69ee..a392acbb2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,10 @@ contact_links: - - name: PrusaSlicer Manual and Support + - name: Do you need Support? + url: https://www.prusa3d.com/page/prusaslicer-support-form_233563/ + about: If you are not sure whether what you are reporting is a bug, please contact our support team first. We are providing full 24/7 customer support. + - name: PrusaSlicer Manual url: https://help.prusa3d.com/en/article/customer-support_2287/ - about: If you are not sure that what you are reporting is really a bug, please, consult the manual first. + about: We have a comprehensive customer support page and help documentation that could be helpful for troubleshooting. - name: PrusaPrinters Forum url: https://forum.prusaprinters.org/forum/prusaslicer/ about: Please get in touch on our PrusaPrinters Community Forum! (Not an official support channel.) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index de5251639..8631d9e17 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -113,8 +113,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const //h^2 = (L / b)^2 [square it] //h^2 = L^2 / b^2 [factor the divisor] const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2)); - coord_t weighted_average_width; - const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width); + const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next); if ((height_2 <= scaled(0.001) //Almost exactly colinear (barring rounding errors). && Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas // We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed @@ -189,8 +188,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const junctions = new_junctions; } -int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width) -{ +int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C) { /* * A B C A C * --------------- ************** @@ -208,27 +206,19 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, * weighted-average width for the entire extrusion line. * * */ - const int64_t ab_length = (B - A).cast().norm(); - const int64_t bc_length = (C - B).cast().norm(); - const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w)); - if (width_diff > 1) - { + const int64_t ab_length = (B.p - A.p).cast().norm(); + const int64_t bc_length = (C.p - B.p).cast().norm(); + if (const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w)); width_diff > 1) { // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong // weighted average value. - const int64_t ab_weight = (A.w + B.w) / 2; - const int64_t bc_weight = (B.w + C.w) / 2; - assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm()) <= std::numeric_limits::max()); - weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast().norm(); - assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits::max())); - return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length; - } - else - { + const int64_t ab_weight = (A.w + B.w) / 2; + const int64_t bc_weight = (B.w + C.w) / 2; + const int64_t weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (ab_length + bc_length); + const int64_t ac_length = (C.p - A.p).cast().norm(); + return std::abs((ab_weight * ab_length + bc_weight * bc_length) - (weighted_average_width * ac_length)); + } else { // If the width difference is very small, then select the width of the segment that is longer - weighted_average_width = ab_length > bc_length ? A.w : B.w; - assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits::max()); - assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits::max()); - return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length; + return ab_length > bc_length ? int64_t(width_diff) * bc_length : int64_t(width_diff) * ab_length; } } diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index c0c5e7db1..d39e1e07b 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -186,9 +186,8 @@ struct ExtrusionLine * \param A Start point of the 3-point-straight line * \param B Intermediate point of the 3-point-straight line * \param C End point of the 3-point-straight line - * \param weighted_average_width The weighted average of the widths of the two colinear extrusion segments * */ - static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width); + static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C); bool is_contour() const; diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 65aa5a333..f24775e22 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -51,7 +51,7 @@ void remove_bad(ExPolygons &expolygons); // helpr for heal shape // Return true when erase otherwise false -bool remove_same_neighbor(Points &points); +bool remove_same_neighbor(Polygon &points); bool remove_same_neighbor(Polygons &polygons); bool remove_same_neighbor(ExPolygons &expolygons); @@ -272,14 +272,22 @@ void priv::remove_bad(ExPolygons &expolygons) { remove_bad(expolygon.holes); } -bool priv::remove_same_neighbor(Slic3r::Points &points) +bool priv::remove_same_neighbor(Slic3r::Polygon &polygon) { + Points &points = polygon.points; if (points.empty()) return false; auto last = std::unique(points.begin(), points.end()); - if (last == points.end()) return false; + + // remove first and last neighbor duplication + if (const Point& last_point = *(last - 1); + last_point == points.front()) { + --last; + } + + // no duplicits + if (last == points.end()) return false; + points.erase(last, points.end()); - // clear points without area - if (points.size() <= 2) points.clear(); return true; } @@ -287,34 +295,30 @@ bool priv::remove_same_neighbor(Polygons &polygons) { if (polygons.empty()) return false; bool exist = false; for (Polygon& polygon : polygons) - exist |= remove_same_neighbor(polygon.points); + exist |= remove_same_neighbor(polygon); // remove empty polygons polygons.erase( std::remove_if(polygons.begin(), polygons.end(), - [](const Polygon &p) { return p.empty(); }), + [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end()); return exist; } bool priv::remove_same_neighbor(ExPolygons &expolygons) { if(expolygons.empty()) return false; - bool exist = false; + bool remove_from_holes = false; + bool remove_from_contour = false; for (ExPolygon &expoly : expolygons) { - exist |= remove_same_neighbor(expoly.contour.points); - Polygons &holes = expoly.holes; - for (Polygon &hole : holes) - exist |= remove_same_neighbor(hole.points); - holes.erase( - std::remove_if(holes.begin(), holes.end(), - [](const Polygon &p) { return p.size() < 3; }), - holes.end()); + remove_from_contour |= remove_same_neighbor(expoly.contour); + remove_from_holes |= remove_same_neighbor(expoly.holes); } - - // Removing of point could create polygon with less than 3 points - if (exist) - remove_bad(expolygons); - - return exist; + // Removing of expolygons without contour + if (remove_from_contour) + expolygons.erase( + std::remove_if(expolygons.begin(), expolygons.end(), + [](const ExPolygon &p) { return p.contour.points.size() <=2; }), + expolygons.end()); + return remove_from_holes || remove_from_contour; } Points priv::collect_close_points(const ExPolygons &expolygons, double distance) { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4156fe4ee..bd4101dc9 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -438,6 +438,10 @@ void GCode::PlaceholderParserIntegration::reset() this->opt_e_position = nullptr; this->opt_e_retracted = nullptr; this->opt_e_restart_extra = nullptr; + this->opt_extruded_volume = nullptr; + this->opt_extruded_weight = nullptr; + this->opt_extruded_volume_total = nullptr; + this->opt_extruded_weight_total = nullptr; this->num_extruders = 0; this->position.clear(); this->e_position.clear(); @@ -463,6 +467,14 @@ void GCode::PlaceholderParserIntegration::init(const GCodeWriter &writer) this->output_config.set_key_value("e_position", opt_e_position); } } + this->opt_extruded_volume = new ConfigOptionFloats(this->num_extruders, 0.f); + this->opt_extruded_weight = new ConfigOptionFloats(this->num_extruders, 0.f); + this->opt_extruded_volume_total = new ConfigOptionFloat(0.f); + this->opt_extruded_weight_total = new ConfigOptionFloat(0.f); + this->parser.set("extruded_volume", this->opt_extruded_volume); + this->parser.set("extruded_weight", this->opt_extruded_weight); + this->parser.set("extruded_volume_total", this->opt_extruded_volume_total); + this->parser.set("extruded_weight_total", this->opt_extruded_weight_total); // Reserve buffer for current position. this->position.assign(3, 0); @@ -484,10 +496,22 @@ void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWri assert(! extruders.empty() && num_extruders == extruders.back().id() + 1); this->e_retracted.assign(num_extruders, 0); this->e_restart_extra.assign(num_extruders, 0); + this->opt_extruded_volume->values.assign(num_extruders, 0); + this->opt_extruded_weight->values.assign(num_extruders, 0); + double total_volume = 0.; + double total_weight = 0.; for (const Extruder &e : extruders) { this->e_retracted[e.id()] = e.retracted(); this->e_restart_extra[e.id()] = e.restart_extra(); + double v = e.extruded_volume(); + double w = v * e.filament_density() * 0.001; + this->opt_extruded_volume->values[e.id()] = v; + this->opt_extruded_weight->values[e.id()] = w; + total_volume += v; + total_weight += w; } + opt_extruded_volume_total->value = total_volume; + opt_extruded_weight_total->value = total_weight; opt_e_retracted->values = this->e_retracted; opt_e_restart_extra->values = this->e_restart_extra; if (! writer.config.use_relative_e_distances) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 98f49095d..346ececba 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -362,6 +362,10 @@ private: ConfigOptionFloats *opt_e_position { nullptr }; ConfigOptionFloats *opt_e_retracted { nullptr }; ConfigOptionFloats *opt_e_restart_extra { nullptr }; + ConfigOptionFloats *opt_extruded_volume { nullptr }; + ConfigOptionFloats *opt_extruded_weight { nullptr }; + ConfigOptionFloat *opt_extruded_volume_total { nullptr }; + ConfigOptionFloat *opt_extruded_weight_total { nullptr }; // Caches of the data passed to the script. size_t num_extruders; std::vector position; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a2c867505..9b371e405 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3508,6 +3508,17 @@ void GCodeProcessor::post_process() ret += buf; } } + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = m_time_processor.machines[i]; + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { + char buf[128]; + sprintf(buf, "; estimated first layer printing time (%s mode) = %s\n", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front()).c_str()); + ret += buf; + } + } } } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 7236f66ff..89add5978 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1033,6 +1033,18 @@ std::tuple, Polygons> generate_extra_perimeters_over }; if (!first_overhang_is_closed_and_anchored) { std::reverse(overhang_region.begin(), overhang_region.end()); + } else { + size_t min_dist_idx = 0; + double min_dist = std::numeric_limits::max(); + for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) { + Point p = overhang_region.front().polyline[i]; + if (double d = lower_layer_aabb_tree.distance_from_lines(p) < min_dist) { + min_dist = d; + min_dist_idx = i; + } + } + std::rotate(overhang_region.front().polyline.begin(), overhang_region.front().polyline.begin() + min_dist_idx, + overhang_region.front().polyline.end()); } auto first_unanchored = std::stable_partition(overhang_region.begin(), overhang_region.end(), is_anchored); int index_of_first_unanchored = first_unanchored - overhang_region.begin(); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 26b359c0e..3f21a80c9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1651,7 +1651,8 @@ void PrintObject::bridge_over_infill() unsupported_area = closing(unsupported_area, SCALED_EPSILON); // By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids // NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole - lower_layer_solids = expand(lower_layer_solids, 3 * spacing); + lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything + lower_layer_solids = expand(lower_layer_solids, (1 + 3) * spacing); // then expand back (opening), and further for parts supported by internal solids // By shrinking the unsupported area, we avoid making bridges from narrow ensuring region along perimeters. unsupported_area = shrink(unsupported_area, 3 * spacing); unsupported_area = diff(unsupported_area, lower_layer_solids); @@ -1825,23 +1826,28 @@ void PrintObject::bridge_over_infill() std::map counted_directions; for (const Polygon &p : bridged_area) { + double acc_distance = 0; for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { Vec2d start = p.points[point_idx].cast(); Vec2d next = p.points[point_idx + 1].cast(); Vec2d v = next - start; // vector from next to current double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a = (start + v * (i * step_size)).cast(); - auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - double angle = lines_tree.get_line(index).orientation(); - if (angle > PI) { - angle -= PI; + acc_distance += dist_to_next; + if (acc_distance > scaled(2.0)) { + acc_distance = 0.0; + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(2.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI * 0.5; + counted_directions[angle]++; } - angle += PI * 0.5; - counted_directions[angle]++; } } } @@ -2083,27 +2089,43 @@ void PrintObject::bridge_over_infill() }; return a.min.x() < b.min.x(); }); + if (surfaces_by_layer[lidx].size() > 2) { + Vec2d origin = get_extents(surfaces_by_layer[lidx].front().new_polys).max.cast(); + std::stable_sort(surfaces_by_layer[lidx].begin() + 1, surfaces_by_layer[lidx].end(), + [origin](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + return (origin - a.min.cast()).squaredNorm() < + (origin - b.min.cast()).squaredNorm(); + }); + } // Gather deep infill areas, where thick bridges fit coordf_t spacing = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).scaled_spacing(); coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).height() * target_flow_height_factor; Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); - // Now also remove area that has been already filled on lower layers by bridging expansion - For this - // reason we did the clustering of layers per thread. - double bottom_z = layer->print_z - target_flow_height - EPSILON; - if (job_idx > 0) { - for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) { - size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx]; - const Layer *lower_layer = po->get_layer(lower_layer_idx); - if (lower_layer->print_z >= bottom_z) { - for (const auto &c : surfaces_by_layer[lower_layer_idx]) { - deep_infill_area = diff(deep_infill_area, c.new_polys); + { + // Now also remove area that has been already filled on lower layers by bridging expansion - For this + // reason we did the clustering of layers per thread. + Polygons filled_polyons_on_lower_layers; + double bottom_z = layer->print_z - target_flow_height - EPSILON; + if (job_idx > 0) { + for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) { + size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx]; + const Layer *lower_layer = po->get_layer(lower_layer_idx); + if (lower_layer->print_z >= bottom_z) { + for (const auto &c : surfaces_by_layer[lower_layer_idx]) { + filled_polyons_on_lower_layers.insert(filled_polyons_on_lower_layers.end(), c.new_polys.begin(), + c.new_polys.end()); + } + } else { + break; } - } else { - break; } } + deep_infill_area = diff(deep_infill_area, filled_polyons_on_lower_layers); } deep_infill_area = expand(deep_infill_area, spacing * 1.5); @@ -2123,9 +2145,8 @@ void PrintObject::bridge_over_infill() Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + - "_total_area", - to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_total_area", + to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); #endif @@ -2186,6 +2207,7 @@ void PrintObject::bridge_over_infill() } bridging_area = opening(bridging_area, flow.scaled_spacing()); + bridging_area = closing(bridging_area, flow.scaled_spacing()); bridging_area = intersection(bridging_area, limiting_area); bridging_area = intersection(bridging_area, total_fill_area); expansion_area = diff(expansion_area, bridging_area); @@ -2220,35 +2242,33 @@ void PrintObject::bridge_over_infill() for (LayerRegion *region : layer->regions()) { Surfaces new_surfaces; + SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); + for (const ExPolygon &ep : new_internal_infills) { + new_surfaces.emplace_back(*internal_infills.front(), ep); + } + + SurfacesPtr internal_solids = region->m_fill_surfaces.filter_by_type(stInternalSolid); for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { - for (Surface &surface : region->m_fill_surfaces.surfaces) { - if (cs.original_surface == &surface) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, cs.new_polys)) { - if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) { - new_surfaces.emplace_back(tmp, expoly); - } - } + for (const Surface *surface : internal_solids) { + if (cs.original_surface == surface) { + Surface tmp{*surface, {}}; tmp.surface_type = stInternalBridge; tmp.bridge_angle = cs.bridge_angle; - for (const ExPolygon &expoly : union_ex(cs.new_polys)) { - new_surfaces.emplace_back(tmp, expoly); + for (const ExPolygon &ep : union_ex(cs.new_polys)) { + new_surfaces.emplace_back(tmp, ep); } - surface.clear(); - } else if (surface.surface_type == stInternal) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { - new_surfaces.emplace_back(tmp, expoly); - } - surface.clear(); + break; } } } - region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); - region->m_fill_surfaces.surfaces.erase(std::remove_if(region->m_fill_surfaces.surfaces.begin(), - region->m_fill_surfaces.surfaces.end(), - [](const Surface &s) { return s.empty(); }), - region->m_fill_surfaces.surfaces.end()); + ExPolygons new_internal_solids = diff_ex(internal_solids, cut_from_infill); + for (const ExPolygon &ep : new_internal_solids) { + new_surfaces.emplace_back(*internal_solids.front(), ep); + } + + region->m_fill_surfaces.remove_types({stInternalSolid, stInternal}); + region->m_fill_surfaces.append(new_surfaces); } } }); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index b73a8c4fb..0e505dfc2 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -31,7 +31,7 @@ #include "I18N.hpp" #include - +#include "format.hpp" namespace Slic3r { @@ -511,8 +511,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. throw Slic3r::RuntimeError( - _u8L("Slicing had to be stopped due to an internal error: " - "Inconsistent slice index.")); + format(_u8L("Model named: %s can not be sliced. Please check if the model is sane."), po.model_object()->name)); po.m_model_height_levels.clear(); po.m_model_height_levels.reserve(po.m_slice_index.size()); diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp index 3d2a00106..ebf275b8f 100644 --- a/src/slic3r/GUI/Downloader.cpp +++ b/src/slic3r/GUI/Downloader.cpp @@ -147,7 +147,7 @@ void Downloader::start_download(const std::string& full_url) std::string escaped_url = FileGet::escape_url(full_url.substr(24)); #endif if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) { - std::string msg = format(_L("Download won't start. Download URL doesn't point to https://files.printables.com : %1%"), escaped_url); + std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url); BOOST_LOG_TRIVIAL(error) << msg; NotificationManager* ntf_mngr = wxGetApp().notification_manager(); ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index bab667676..31bd299e1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -22,6 +22,7 @@ #include "libslic3r/NSVGUtils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Preset.hpp" #include "libslic3r/ClipperUtils.hpp" // union_ex #include "libslic3r/AppConfig.hpp" // store/load font list #include "libslic3r/Format/OBJ.hpp" // load obj file for default object @@ -1326,7 +1327,7 @@ void GLGizmoEmboss::draw_text_input() // show warning about incorrectness view of font std::string warning_tool_tip; if (!exist_font) { - warning_tool_tip = _u8L("Can't write text by selected font.Try to choose another font."); + warning_tool_tip = _u8L("The text cannot be written using the selected font. Please try choosing a different font."); } else { auto append_warning = [&warning_tool_tip](std::string t) { if (!warning_tool_tip.empty()) @@ -1335,15 +1336,15 @@ void GLGizmoEmboss::draw_text_input() }; if (priv::is_text_empty(m_text)) - append_warning(_u8L("Embossed text can NOT contain only white spaces.")); + append_warning(_u8L("Embossed text cannot contain only white spaces.")); if (m_text_contain_unknown_glyph) - append_warning(_u8L("Text contain character glyph (represented by '?') unknown by font.")); + append_warning(_u8L("Text contains character glyph (represented by '?') unknown by font.")); const FontProp &prop = m_style_manager.get_font_prop(); - if (prop.skew.has_value()) append_warning(_u8L("Text input do not show font skew.")); - if (prop.boldness.has_value()) append_warning(_u8L("Text input do not show font boldness.")); + if (prop.skew.has_value()) append_warning(_u8L("Text input doesn't show font skew.")); + if (prop.boldness.has_value()) append_warning(_u8L("Text input doesn't show font boldness.")); if (prop.line_gap.has_value()) - append_warning(_u8L("Text input do not show gap between lines.")); + append_warning(_u8L("Text input doesn't show gap between lines.")); auto &ff = m_style_manager.get_font_file_with_cache(); float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale); if (imgui_size > StyleManager::max_imgui_font_size) @@ -1959,7 +1960,7 @@ void GLGizmoEmboss::draw_font_list() void GLGizmoEmboss::draw_model_type() { bool is_last_solid_part = m_volume->is_the_only_one_part(); - std::string title = _u8L("Text is to object"); + std::string title = _u8L("Operation"); if (is_last_solid_part) { ImVec4 color{.5f, .5f, .5f, 1.f}; m_imgui->text_colored(color, title.c_str()); @@ -1973,14 +1974,15 @@ void GLGizmoEmboss::draw_model_type() ModelVolumeType part = ModelVolumeType::MODEL_PART; ModelVolumeType type = m_volume->type(); - if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) + //TRN EmbossOperation + if (ImGui::RadioButton(_u8L("Join").c_str(), type == part)) new_type = part; else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); ImGui::SameLine(); std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); - if (ImGui::RadioButton(_u8L("Subtracted").c_str(), type == negative)) + if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative)) new_type = negative; else if (ImGui::IsItemHovered()) { if (is_last_solid_part) @@ -2187,7 +2189,7 @@ void GLGizmoEmboss::draw_style_add_button() }else if (only_add_style) { ImGui::SetTooltip("%s", _u8L("Add style to my list.").c_str()); } else { - ImGui::SetTooltip("%s", _u8L("Add as new named style.").c_str()); + ImGui::SetTooltip("%s", _u8L("Save as new style.").c_str()); } } @@ -2203,32 +2205,47 @@ void GLGizmoEmboss::draw_delete_style_button() { bool is_last = m_style_manager.get_styles().size() == 1; bool can_delete = is_stored && !is_last; - std::string title = _u8L("Remove style"); - const char * popup_id = title.c_str(); - static size_t next_style_index = std::numeric_limits::max(); if (draw_button(m_icons, IconType::erase, !can_delete)) { + std::string style_name = m_style_manager.get_style().name; // copy + wxString dialog_title = _L("Remove style"); + size_t next_style_index = std::numeric_limits::max(); + Plater *plater = wxGetApp().plater(); + bool exist_change = false; while (true) { // NOTE: can't use previous loaded activ index -> erase could change index size_t active_index = m_style_manager.get_style_index(); next_style_index = (active_index > 0) ? active_index - 1 : active_index + 1; + if (next_style_index >= m_style_manager.get_styles().size()) { - // can't remove last font style - // TODO: inform user + MessageDialog msg(plater, _L("Can't remove the last exising style."), dialog_title, wxICON_ERROR | wxOK); + msg.ShowModal(); break; } + // IMPROVE: add function can_load? // clean unactivable styles if (!m_style_manager.load_style(next_style_index)) { m_style_manager.erase(next_style_index); + exist_change = true; continue; } - // load back - m_style_manager.load_style(active_index); - ImGui::OpenPopup(popup_id); + wxString message = GUI::format_wxstr(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove \"%1%\" style?"), style_name); + MessageDialog msg(plater, message, dialog_title, wxICON_WARNING | wxYES | wxNO); + if (msg.ShowModal() == wxID_YES) { + // delete style + m_style_manager.erase(active_index); + exist_change = true; + process(); + } else { + // load back style + m_style_manager.load_style(active_index); + } break; } + if (exist_change) + m_style_manager.store_styles_to_app_config(wxGetApp().app_config); } if (ImGui::IsItemHovered()) { @@ -2239,25 +2256,6 @@ void GLGizmoEmboss::draw_delete_style_button() { else/*if(!is_stored)*/ tooltip = GUI::format(_L("Can't delete temporary style \"%1%\"."), style_name); ImGui::SetTooltip("%s", tooltip.c_str()); } - - if (ImGui::BeginPopupModal(popup_id)) { - m_imgui->disable_background_fadeout_animation(); - const std::string &style_name = m_style_manager.get_style().name; - std::string text_in_popup = GUI::format(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove style \"%1%\"?"), style_name); - ImGui::Text("%s", text_in_popup.c_str()); - if (ImGui::Button(_u8L("Yes").c_str())) { - size_t active_index = m_style_manager.get_style_index(); - m_style_manager.load_style(next_style_index); - m_style_manager.erase(active_index); - m_style_manager.store_styles_to_app_config(wxGetApp().app_config); - ImGui::CloseCurrentPopup(); - process(); - } - ImGui::SameLine(); - if (ImGui::Button(_u8L("No").c_str())) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } } // FIX IT: it should not change volume position before successfull change @@ -2304,7 +2302,7 @@ void GLGizmoEmboss::draw_style_list() { trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width); } - std::string title = _u8L("Presets"); + std::string title = _u8L("Styles"); if (m_style_manager.exist_stored_style()) ImGui::Text("%s", title.c_str()); else @@ -2313,7 +2311,7 @@ void GLGizmoEmboss::draw_style_list() { ImGui::SetNextItemWidth(m_gui_cfg->input_width); auto add_text_modify = [&is_modified](const std::string& name) { if (!is_modified) return name; - return name + " (" + _u8L("modified") + ")"; + return name + Preset::suffix_modified(); }; std::optional selected_style_index; if (ImGui::BeginCombo("##style_selector", add_text_modify(trunc_name).c_str())) { @@ -2846,17 +2844,17 @@ void GLGizmoEmboss::draw_advanced() } m_imgui->disabled_end(); // !can_use_surface // TRN EmbossGizmo: font units - std::string units = _u8L("font points"); + std::string units = _u8L("points"); std::string units_fmt = "%.0f " + units; - // input gap between letters + // input gap between characters auto def_char_gap = stored_style ? &stored_style->prop.char_gap : nullptr; int half_ascent = font_info.ascent / 2; int min_char_gap = -half_ascent, max_char_gap = half_ascent; - if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), - min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ + if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between characters"), + min_char_gap, max_char_gap, units_fmt, _L("Distance between characters"))){ // Condition prevent recalculation when insertint out of limits value by imgui input if (!priv::Limits::apply(font_prop.char_gap, priv::limits.char_gap) || !m_volume->text_configuration->style.prop.char_gap.has_value() || @@ -2918,7 +2916,7 @@ void GLGizmoEmboss::draw_advanced() m_imgui->disabled_begin(!allowe_surface_distance); const std::string undo_move_tooltip = _u8L("Undo translation"); - const wxString move_tooltip = _L("Distance of the center of text from model surface"); + const wxString move_tooltip = _L("Distance of the center of the text to the model surface."); bool is_moved = false; bool use_inch = wxGetApp().app_config->get_bool("use_inches"); if (use_inch) { diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 826dc2407..8115136a5 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -212,49 +212,6 @@ static coord_t brim_offset(const PrintObject &po, const ModelInstance &inst) return has_outer_brim ? scaled(brim_width + brim_separation) : 0; } -template -Polygon support_layers_chull (Points &pts, It from_lyr, It to_lyr) { - - size_t cap = 0; - for (auto it = from_lyr; it != to_lyr; ++it) { - for (const ExPolygon &expoly : (*it)->support_islands) - cap += expoly.contour.points.size(); - } - - pts.reserve(pts.size() + cap); - - for (auto it = from_lyr; it != to_lyr; ++it) { - for (const ExPolygon &expoly : (*it)->support_islands) - std::copy(expoly.contour.begin(), expoly.contour.end(), - std::back_inserter(pts)); - } - - Polygon ret = Geometry::convex_hull(pts); - - return ret; -} - -static void update_arrangepoly_fffprint(arrangement::ArrangePolygon &ret, - const PrintObject &po, - const ModelInstance &inst) -{ - auto laststep = po.last_completed_step(); - - coord_t infl = brim_offset(po, inst); - - if (laststep < posCount && laststep > posSupportMaterial) { - Points pts = std::move(ret.poly.contour.points); - Polygon poly = support_layers_chull(pts, - po.support_layers().begin(), - po.support_layers().end()); - - ret.poly.contour = std::move(poly); - ret.poly.holes = {}; - } - - ret.inflation = infl; -} - arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) { arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater); @@ -442,7 +399,7 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, plater->fff_print().get_print_object_by_model_object_id(obj_id); if (po) { - update_arrangepoly_fffprint(ap, *po, *inst); + ap.inflation = brim_offset(*po, *inst); } } diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index b6881f1ae..ecd8e0727 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -13,6 +13,11 @@ #include #include #include + +#include // include before devpropdef.h +#include +#include +#include #else // unix, linux & OSX includes #include @@ -73,6 +78,287 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons } namespace { +int eject_alt(const std::wstring& volume_access_path) +{ + HANDLE handle = CreateFileW(volume_access_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (handle == INVALID_HANDLE_VALUE): " << GetLastError(); + return 1; + } + DWORD deviceControlRetVal(0); + //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger. + //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont + BOOL e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + BOOST_LOG_TRIVIAL(debug) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError(); + BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + BOOST_LOG_TRIVIAL(debug) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError(); + + // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval + BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + if (error == 0) { + CloseHandle(handle); + BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError(); + return 1; + } + CloseHandle(handle); + BOOST_LOG_TRIVIAL(info) << "Alt Ejecting finished"; + return 0; +} + + +// From https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview +typedef struct _STRING_DESCRIPTOR_NODE +{ + struct _STRING_DESCRIPTOR_NODE* Next; + UCHAR DescriptorIndex; + USHORT LanguageID; + USB_STRING_DESCRIPTOR StringDescriptor[1]; +} STRING_DESCRIPTOR_NODE, * PSTRING_DESCRIPTOR_NODE; + +// Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview +PSTRING_DESCRIPTOR_NODE GetStringDescriptor( + HANDLE handle_hub_device, + ULONG connection_index, + UCHAR descriptor_index, + USHORT language_ID +) +{ + BOOL success = 0; + ULONG nbytes = 0; + ULONG nbytes_returned = 0; + UCHAR string_desc_req_buf[sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH]; + PUSB_DESCRIPTOR_REQUEST string_desc_req = NULL; + PUSB_STRING_DESCRIPTOR string_desc = NULL; + PSTRING_DESCRIPTOR_NODE string_desc_node = NULL; + + nbytes = sizeof(string_desc_req_buf); + string_desc_req = (PUSB_DESCRIPTOR_REQUEST)string_desc_req_buf; + string_desc = (PUSB_STRING_DESCRIPTOR)(string_desc_req + 1); + + // Zero fill the entire request structure + memset(string_desc_req, 0, nbytes); + + // Indicate the port from which the descriptor will be requested + string_desc_req->ConnectionIndex = connection_index; + + // USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this + // IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request. + // + // USBD will automatically initialize these fields: + // bmRequest = 0x80 + // bRequest = 0x06 + // + // We must inititialize these fields: + // wValue = Descriptor Type (high) and Descriptor Index (low byte) + // wIndex = Zero (or Language ID for String Descriptors) + // wLength = Length of descriptor buffer + string_desc_req->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) + | descriptor_index; + string_desc_req->SetupPacket.wIndex = language_ID; + string_desc_req->SetupPacket.wLength = (USHORT)(nbytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + // Now issue the get descriptor request. + success = DeviceIoControl(handle_hub_device, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + string_desc_req, + nbytes, + string_desc_req, + nbytes, + &nbytes_returned, + NULL); + + // Do some sanity checks on the return from the get descriptor request. + if (!success) { + return NULL; + } + if (nbytes_returned < 2) { + return NULL; + } + if (string_desc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE) { + return NULL; + } + if (string_desc->bLength != nbytes_returned - sizeof(USB_DESCRIPTOR_REQUEST)) { + return NULL; + } + if (string_desc->bLength % 2 != 0) { + return NULL; + } + + // Looks good, allocate some (zero filled) space for the string descriptor + // node and copy the string descriptor to it. + string_desc_node = (PSTRING_DESCRIPTOR_NODE)malloc(sizeof(STRING_DESCRIPTOR_NODE) + string_desc->bLength * sizeof(DWORD)); + if (string_desc_node == NULL) { + return NULL; + } + string_desc_node->Next = NULL; + string_desc_node->DescriptorIndex = descriptor_index; + string_desc_node->LanguageID = language_ID; + + memcpy(string_desc_node->StringDescriptor, + string_desc, + string_desc->bLength); + + return string_desc_node; +} + +// Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview +HRESULT GetStringDescriptors( + _In_ HANDLE handle_hub_device, + _In_ ULONG connection_index, + _In_ UCHAR descriptor_index, + _In_ ULONG num_language_IDs, + _In_reads_(num_language_IDs) USHORT* language_IDs, + _In_ PSTRING_DESCRIPTOR_NODE string_desc_node_head, + std::wstring& result +) +{ + PSTRING_DESCRIPTOR_NODE tail = NULL; + PSTRING_DESCRIPTOR_NODE trailing = NULL; + ULONG i = 0; + + // Go to the end of the linked list, searching for the requested index to + // see if we've already retrieved it + for (tail = string_desc_node_head; tail != NULL; tail = tail->Next) { + if (tail->DescriptorIndex == descriptor_index) { + // copy string descriptor to result + for(int i = 0; i < tail->StringDescriptor->bLength / 2 - 1; i++) { + result += tail->StringDescriptor->bString[i]; + } + return S_OK; + } + trailing = tail; + } + tail = trailing; + + // Get the next String Descriptor. If this is NULL, then we're done (return) + // Otherwise, loop through all Language IDs + for (i = 0; (tail != NULL) && (i < num_language_IDs); i++) { + tail->Next = GetStringDescriptor(handle_hub_device, + connection_index, + descriptor_index, + language_IDs[i]); + tail = tail->Next; + } + + if (tail == NULL) { + return E_FAIL; + } else { + // copy string descriptor to result + for (int i = 0; i < tail->StringDescriptor->bLength / 2 - 1; i++) { + result += tail->StringDescriptor->bString[i]; + } + return S_OK; + } +} + +bool get_handle_from_devinst(DEVINST devinst, HANDLE& handle) +{ + // create path consisting of device id and guid + wchar_t device_id[MAX_PATH]; + CM_Get_Device_ID(devinst, device_id, MAX_PATH, 0); + + //convert device id string to device path - https://stackoverflow.com/a/32641140/981766 + std::wstring dev_id_wstr(device_id); + dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(LR"(\\)"), L"#"); // '\' is special for regex + dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(L"^"), LR"(\\?\)", std::regex_constants::format_first_only); + dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(L"$"), L"#", std::regex_constants::format_first_only); + + // guid + wchar_t guid_wchar[64];//guid is 32 chars+4 hyphens+2 paranthesis+null => 64 should be more than enough + StringFromGUID2(GUID_DEVINTERFACE_USB_HUB, guid_wchar, 64); + dev_id_wstr.append(guid_wchar); + + // get handle + std::wstring& usb_hub_path = dev_id_wstr; + handle = CreateFileW(usb_hub_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) { + // Sometimes device is not GUID_DEVINTERFACE_USB_HUB, than we need to check parent recursively + DEVINST parent_devinst = 0; + if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS) + return false; + return get_handle_from_devinst(parent_devinst, handle); + } + return true; +} + +// Read Configuration Descriptor - configuration string indexed by iConfiguration and decide if card reader +bool is_card_reader(HDEVINFO h_dev_info, SP_DEVINFO_DATA& spdd) +{ + // First get port number of device. + + DEVINST parent_devinst = 0; + HANDLE handle; // usb hub handle + DWORD usb_port_number = 0; + DWORD required_size = 0; + // First we need handle for GUID_DEVINTERFACE_USB_HUB device. + if (CM_Get_Parent(&parent_devinst, spdd.DevInst, 0) != CR_SUCCESS) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get parent DEVINST."; + return false; + } + if(!get_handle_from_devinst(parent_devinst, handle) || handle == INVALID_HANDLE_VALUE) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get HANDLE for parent DEVINST."; + return false; + } + // Get port number to which the usb device is attached on the hub. + if (SetupDiGetDeviceRegistryProperty(h_dev_info, &spdd, SPDRP_ADDRESS, nullptr, (PBYTE)&usb_port_number, sizeof(usb_port_number), &required_size) == 0) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get port number."; + return false; + } + + // Fill USB request packet to get iConfiguration value. + + int buffer_size = sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_CONFIGURATION_DESCRIPTOR); + BYTE* buffer = new BYTE[buffer_size]; + USB_DESCRIPTOR_REQUEST* request_packet = (USB_DESCRIPTOR_REQUEST*)buffer; + USB_CONFIGURATION_DESCRIPTOR* configuration_descriptor = (USB_CONFIGURATION_DESCRIPTOR*)((BYTE*)buffer + sizeof(USB_DESCRIPTOR_REQUEST)); + DWORD bytes_returned = 0; + // Fill information in packet. + request_packet->SetupPacket.bmRequest = 0x80; + request_packet->SetupPacket.bRequest = USB_REQUEST_GET_CONFIGURATION; + request_packet->ConnectionIndex = usb_port_number; + request_packet->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8 | 0 /*Since only 1 device descriptor => index : 0*/); + request_packet->SetupPacket.wLength = sizeof(USB_CONFIGURATION_DESCRIPTOR); + // Issue ioctl. + if (DeviceIoControl(handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buffer, buffer_size, buffer, buffer_size, &bytes_returned, nullptr) == 0) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get Configuration Descriptor."; + return false; + } + // Nothing to read. + if (configuration_descriptor->iConfiguration == 0) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: iConfiguration value is 0."; + return false; + } + + // Get string descriptor and read string on address given by iConfiguration index . + // Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview + + PSTRING_DESCRIPTOR_NODE supported_languages_string = NULL; + ULONG num_language_IDs = 0; + USHORT* language_IDs = NULL; + std::wstring configuration_string; + // Get languages. + supported_languages_string = GetStringDescriptor(handle, usb_port_number, 0, 0); + if (supported_languages_string == NULL) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get language string descriptor."; + return false; + } + num_language_IDs = (supported_languages_string->StringDescriptor->bLength - 2) / 2; + language_IDs = (USHORT*)&supported_languages_string->StringDescriptor->bString[0]; + // Get configration string. + if (GetStringDescriptors(handle, usb_port_number, configuration_descriptor->iConfiguration, num_language_IDs, language_IDs, supported_languages_string, configuration_string) == E_FAIL) { + BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get configuration string descriptor."; + return false; + } + + // Final compare. + BOOST_LOG_TRIVIAL(error) << "Ejecting information: Retrieved configuration string: " << configuration_string; + if (configuration_string.find(L"CARD READER") != std::wstring::npos) { + BOOST_LOG_TRIVIAL(info) << "Detected external reader."; + return true; + } + return false; +} + // returns the device instance handle of a storage volume or 0 on error // called from eject_inner, based on https://stackoverflow.com/a/58848961 DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR* dos_device_name) @@ -80,7 +366,7 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR bool is_floppy = (wcsstr(dos_device_name, L"\\Floppy") != NULL); // TODO: could be tested better? if (drive_type != DRIVE_REMOVABLE || is_floppy) { - BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is not removable."; + BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Drive is not removable."; return 0; } @@ -89,7 +375,7 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR HDEVINFO h_dev_info = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (h_dev_info == INVALID_HANDLE_VALUE) { - BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Invalid dev info handle."; + BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Invalid dev info handle."; return 0; } @@ -135,13 +421,16 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR if (device_number != (long)sdn.DeviceNumber) { continue; } - // this is the drive, return the device instance + + // check if is sd card reader - if yes, indicate by returning invalid value. + bool reader = is_card_reader(h_dev_info, spdd); + SetupDiDestroyDeviceInfoList(h_dev_info); - return spdd.DevInst; + return !reader ? spdd.DevInst : 0; } SetupDiDestroyDeviceInfoList(h_dev_info); - BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive."; + BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive."; return 0; } @@ -194,10 +483,9 @@ int eject_inner(const std::string& path) // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number DEVINST dev_inst = get_dev_inst_by_device_number(device_number, drive_type, dos_device_name); - if (dev_inst == 0) { - BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid device instance handle.", path); - return 1; + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1%: Invalid device instance handle. Going to try alternative ejecting method.", path); + return eject_alt(volume_access_path); } PNP_VETO_TYPE veto_type = PNP_VetoTypeUnknown; @@ -249,7 +537,7 @@ int eject_inner(const std::string& path) return 1; } -} +} // namespace // Called from UI therefore it blocks the UI thread. // It also blocks updates at the worker thread. // Win32 implementation. @@ -268,18 +556,21 @@ void RemovableDriveManager::eject_drive() if (it_drive_data != m_current_drives.end()) { if (!eject_inner(m_last_save_path)) { // success - assert(m_callback_evt_handler); - if (m_callback_evt_handler) - wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true))); + BOOST_LOG_TRIVIAL(info) << "Ejecting has succeeded."; + assert(m_callback_evt_handler); + if (m_callback_evt_handler) + wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true))); } else { // failed to eject // this should not happen, throwing exception might be the way here + BOOST_LOG_TRIVIAL(error) << "Ejecting has failed."; assert(m_callback_evt_handler); if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); } } else { // drive not found in m_current_drives + BOOST_LOG_TRIVIAL(error) << "Ejecting has failed. Drive not found in m_current_drives."; assert(m_callback_evt_handler); if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair({"",""}, false)));