Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_transformations
This commit is contained in:
commit
3c8ecf5bd6
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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.)
|
||||
|
@ -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<coord_t>(0.001) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(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<int64_t>().norm();
|
||||
const int64_t bc_length = (C - B).cast<int64_t>().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<int64_t>().norm();
|
||||
const int64_t bc_length = (C.p - B.p).cast<int64_t>().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<int64_t>().norm()) <= std::numeric_limits<coord_t>::max());
|
||||
weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().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<int64_t>::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<int64_t>().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<coord_t>::max());
|
||||
assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits<coord_t>::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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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<double> position;
|
||||
|
@ -3508,6 +3508,17 @@ void GCodeProcessor::post_process()
|
||||
ret += buf;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = m_time_processor.machines[i];
|
||||
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1033,6 +1033,18 @@ std::tuple<std::vector<ExtrusionPaths>, 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<double>::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<true>(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();
|
||||
|
@ -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<double, int> 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<double>();
|
||||
Vec2d next = p.points[point_idx + 1].cast<double>();
|
||||
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<coord_t>();
|
||||
auto [distance, index, p] = lines_tree.distance_from_lines_extra<false>(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<coord_t>();
|
||||
auto [distance, index, p] = lines_tree.distance_from_lines_extra<false>(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<double>();
|
||||
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<double>()).squaredNorm() <
|
||||
(origin - b.min.cast<double>()).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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
#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());
|
||||
|
@ -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);
|
||||
|
@ -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<size_t>::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<size_t>::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<size_t> 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) {
|
||||
|
@ -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<class It>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,11 @@
|
||||
#include <Dbt.h>
|
||||
#include <Setupapi.h>
|
||||
#include <cfgmgr32.h>
|
||||
|
||||
#include <initguid.h> // include before devpropdef.h
|
||||
#include <devpropdef.h>
|
||||
#include <devpkey.h>
|
||||
#include <usbioctl.h>
|
||||
#else
|
||||
// unix, linux & OSX includes
|
||||
#include <errno.h>
|
||||
@ -73,6 +78,287 @@ std::vector<DriveData> 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<DriveData, bool>(*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<DriveData, bool>({"",""}, false)));
|
||||
|
Loading…
Reference in New Issue
Block a user