Merge branch 'main' into enh-update-wxwidgets

This commit is contained in:
Ocraftyone
2023-11-13 00:58:46 -05:00
163 changed files with 4674 additions and 2270 deletions
+1 -2
View File
@@ -22,9 +22,8 @@ public:
} else {
m_E = 0;
m_retracted = 0;
m_restart_extra = 0;
}
m_restart_extra = 0;
m_absolute_E = 0;
}
+1 -1
View File
@@ -645,7 +645,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
}
// BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead
/*if (layer.object()->config().detect_narrow_internal_solid_infill)*/ {
if (layer.object()->config().detect_narrow_internal_solid_infill) {
size_t surface_fills_size = surface_fills.size();
for (size_t i = 0; i < surface_fills_size; i++) {
if (surface_fills[i].surface.surface_type != stInternalSolid)
+47 -5
View File
@@ -710,7 +710,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
if (is_ramming)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
if (gcodegen.config().has_prime_tower)
if (gcodegen.config().enable_prime_tower)
deretraction_str = gcodegen.unretract();
}
@@ -925,7 +925,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
assert(m_layer_idx >= 0);
if (m_layer_idx >= (int) m_tool_changes.size())
return gcode;
if (gcodegen.config().purge_in_prime_tower) {
if (!gcodegen.is_BBL_Printer()) {
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < (int) m_tool_changes.size()) {
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
@@ -1206,8 +1206,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
|| (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
double top_cd = object.config().support_top_z_distance;
//double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance;
double bottom_cd = top_cd;
double bottom_cd = object.config().support_bottom_z_distance;
double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd);
@@ -1402,6 +1401,8 @@ namespace DoExport {
//BBS
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value);
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value);
//Orca
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change extrusion role G-code")), config.change_extrusion_role_gcode.value);
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value);
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value);
if (ret.size() < MAX_TAGS_COUNT) {
@@ -2187,7 +2188,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
bbox_wo_wt.merge(unscaled(objPtr->get_first_layer_bbox(data.area, data.layer_height, data.name)));
}
auto center = bbox_wo_wt.center();
this->placeholder_parser().set("first_layer_center_no_wipe_tower", new ConfigOptionFloats(center.x(), center.y()));
this->placeholder_parser().set("first_layer_center_no_wipe_tower", new ConfigOptionFloats({center.x(), center.y()}));
}
bool activate_chamber_temp_control = false;
auto max_chamber_temp = 0;
@@ -4755,6 +4756,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double F = speed * 60; // convert mm/sec to mm/min
//Orca: process custom gcode for extrusion role change
if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(path.role())));
config.set_key_value("last_extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(m_last_extrusion_role)));
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z));
gcode += this->placeholder_parser_process("change_extrusion_role_gcode",
m_config.change_extrusion_role_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
}
// extrude arc or line
if (m_enable_extrusion_role_markers) {
if (path.role() != m_last_extrusion_role) {
@@ -5002,6 +5015,35 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
return gcode;
}
//Orca: get string name of extrusion role. used for change_extruder_role_gcode
std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & role)
{
switch (role) {
case erPerimeter: return "Perimeter";
case erExternalPerimeter: return "ExternalPerimeter";
case erOverhangPerimeter: return "OverhangPerimeter";
case erInternalInfill: return "InternalInfill";
case erSolidInfill: return "SolidInfill";
case erTopSolidInfill: return "TopSolidInfill";
case erBottomSurface: return "BottomSurface";
case erBridgeInfill:
case erInternalBridgeInfill: return "BridgeInfill";
case erGapFill: return "GapFill";
case erIroning: return "Ironing";
case erSkirt: return "Skirt";
case erBrim: return "Brim";
case erSupportMaterial: return "SupportMaterial";
case erSupportMaterialInterface: return "SupportMaterialInterface";
case erSupportTransition: return "SupportTransition";
case erWipeTower: return "WipeTower";
case erCustom:
case erMixed:
case erCount:
case erNone:
default: return "Mixed";
}
}
std::string encodeBase64(uint64_t value)
{
//Always use big endian mode
+2 -1
View File
@@ -205,6 +205,7 @@ public:
// inside the generated string and after the G-code export finishes.
std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
bool enable_cooling_markers() const { return m_enable_cooling_markers; }
std::string extrusion_role_to_string_for_parser(const ExtrusionRole &);
// For Perl bindings, to be used exclusively by unit tests.
unsigned int layer_count() const { return m_layer_count; }
@@ -213,7 +214,7 @@ public:
std::string travel_to(const Point& point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::SpiralLift);
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
std::string set_extruder(unsigned int extruder_id, double print_z);
bool is_BBL_Printer();
+160 -101
View File
@@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "../Layer.hpp"
#include "../GCode.hpp"
#include "../EdgeGrid.hpp"
@@ -13,6 +17,8 @@
#include <unordered_set>
#include <boost/range/adaptor/reversed.hpp>
//#define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
namespace Slic3r {
struct TravelPoint
@@ -20,6 +26,8 @@ struct TravelPoint
Point point;
// Index of the polygon containing this point. A negative value indicates that the point is not on any border.
int border_idx;
// simplify_travel() doesn't remove this point.
bool do_not_remove = false;
};
struct Intersection
@@ -32,6 +40,8 @@ struct Intersection
Point point;
// Distance from the first point in the corresponding boundary
float distance;
// simplify_travel() doesn't remove this point.
bool do_not_remove = false;
};
struct ClosestLine
@@ -207,8 +217,8 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
const ClosestLine &cl_start = start_lines[cl_indices.first];
const ClosestLine &cl_end = end_lines[cl_indices.second];
std::vector<Intersection> new_intersections;
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true});
return new_intersections;
}
}
@@ -259,7 +269,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_start = start_lines[cl_start_idx];
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)};
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
@@ -267,7 +277,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// use the first one, which is the closest one to the start point.
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, new_intersections, true);
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true});
}
}
@@ -276,7 +286,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_end = end_lines[cl_end_idx];
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)};
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
@@ -284,7 +294,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// use the first one, which is the closest one to the end point.
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, new_intersections, false);
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true});
}
}
return new_intersections;
@@ -350,8 +360,6 @@ static Polyline to_polyline(const std::vector<TravelPoint> &travel)
return result;
}
// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
static void export_travel_to_svg(const Polygons &boundary,
const Line &original_travel,
@@ -359,16 +367,17 @@ static void export_travel_to_svg(const Polygons &boundary,
const std::vector<Intersection> &intersections,
const std::string &path)
{
BoundingBox bbox = get_extents(boundary);
coordf_t stroke_width = scale_(0.05);
BoundingBox bbox = get_extents(boundary);
::Slic3r::SVG svg(path, bbox);
svg.draw_outline(boundary, "green");
svg.draw(original_travel, "blue");
svg.draw(result_travel, "red");
svg.draw(original_travel.a, "black");
svg.draw(original_travel.b, "grey");
svg.draw_outline(boundary, "green", stroke_width);
svg.draw(original_travel, "blue", stroke_width);
svg.draw(result_travel, "red", stroke_width);
svg.draw(original_travel.a, "black", stroke_width);
svg.draw(original_travel.b, "grey", stroke_width);
for (const Intersection &intersection : intersections)
svg.draw(intersection.point, "lightseagreen");
svg.draw(intersection.point, "lightseagreen", stroke_width);
}
static void export_travel_to_svg(const Polygons &boundary,
@@ -433,21 +442,30 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
visitor.pt_current = &current_point;
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
if (travel[point_idx_2].point == current_point) {
next = travel[point_idx_2];
point_idx = point_idx_2;
continue;
}
if (!next.do_not_remove)
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
// Workaround for some issue in MSVC 19.29.30037 32-bit compiler.
#if defined(_WIN32) && !defined(_WIN64)
if (bool volatile do_not_remove = travel[point_idx_2].do_not_remove; do_not_remove)
break;
#else
if (travel[point_idx_2].do_not_remove)
break;
#endif
if (travel[point_idx_2].point == current_point) {
next = travel[point_idx_2];
point_idx = point_idx_2;
continue;
}
visitor.pt_next = &travel[point_idx_2].point;
boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
// Check if deleting point causes crossing a boundary
if (!visitor.intersect) {
next = travel[point_idx_2];
point_idx = point_idx_2;
visitor.pt_next = &travel[point_idx_2].point;
boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
// Check if deleting point causes crossing a boundary
if (!visitor.intersect) {
next = travel[point_idx_2];
point_idx = point_idx_2;
}
}
}
simplified_path.emplace_back(next);
}
@@ -473,7 +491,7 @@ static float get_perimeter_spacing(const Layer &layer)
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
if (layer_region != nullptr && ! layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++regions_count;
}
@@ -494,7 +512,7 @@ static float get_perimeter_spacing_external(const Layer &layer)
for (const PrintObject *object : layer.object()->print()->objects())
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
for (const LayerRegion *layer_region : l->regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
if (layer_region != nullptr && ! layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++ regions_count;
}
@@ -507,6 +525,25 @@ static float get_perimeter_spacing_external(const Layer &layer)
return perimeter_spacing;
}
// Returns average perimeter width calculated from all LayerRegion within the layer.
static float get_external_perimeter_width(const Layer &layer)
{
size_t regions_count = 0;
float perimeter_width = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && ! layer_region->slices.empty()) {
perimeter_width += float(layer_region->flow(frExternalPerimeter).scaled_width());
++regions_count;
}
assert(perimeter_width >= 0.f);
if (regions_count != 0)
perimeter_width /= float(regions_count);
else
perimeter_width = get_default_perimeter_spacing(*layer.object());
return perimeter_width;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
@@ -566,7 +603,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
// Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the
// boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the
// appended point will be inside the polygon and not on the polygon border.
result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)});
result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx), intersection_first.do_not_remove});
// Check if intersection line also exit the boundary polygon
if (it_second_r != it_last_item) {
@@ -590,7 +627,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
// Append the farthest intersection into the path
left_idx = intersection_second.line_idx;
right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1);
result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)});
result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx), intersection_second.do_not_remove});
// Skip intersections in between
it_first = it_second;
}
@@ -645,22 +682,22 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
// Check if anyone of ExPolygons contains whole travel.
// called by need_wipe() and AvoidCrossingPerimeters::travel_to()
// FIXME Lukas H.: Maybe similar approach could also be used for ExPolygon::contains()
static bool any_expolygon_contains(const ExPolygons &ex_polygons,
const std::vector<BoundingBox> &ex_polygons_bboxes,
const EdgeGrid::Grid &grid_lslice,
static bool any_expolygon_contains(const ExPolygons &lslices_offset,
const std::vector<BoundingBox> &lslices_offset_bboxes,
const EdgeGrid::Grid &grid_lslices_offset,
const Line &travel)
{
assert(ex_polygons.size() == ex_polygons_bboxes.size());
if(!grid_lslice.bbox().contains(travel.a) || !grid_lslice.bbox().contains(travel.b))
assert(lslices_offset.size() == lslices_offset_bboxes.size());
if(!grid_lslices_offset.bbox().contains(travel.a) || !grid_lslices_offset.bbox().contains(travel.b))
return false;
FirstIntersectionVisitor visitor(grid_lslice);
FirstIntersectionVisitor visitor(grid_lslices_offset);
visitor.pt_current = &travel.a;
visitor.pt_next = &travel.b;
grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
grid_lslices_offset.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
if (!visitor.intersect) {
for (const ExPolygon &ex_polygon : ex_polygons) {
const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()];
for (const ExPolygon &ex_polygon : lslices_offset) {
const BoundingBox &bbox = lslices_offset_bboxes[&ex_polygon - &lslices_offset.front()];
if (bbox.contains(travel.a) && bbox.contains(travel.b) && ex_polygon.contains(travel.a))
return true;
}
@@ -670,18 +707,18 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons,
// Check if anyone of ExPolygons contains whole travel.
// called by need_wipe()
static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vector<BoundingBox> &ex_polygons_bboxes, const EdgeGrid::Grid &grid_lslice, const Polyline &travel)
static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vector<BoundingBox> &ex_polygons_bboxes, const EdgeGrid::Grid &grid_lslice_offset, const Polyline &travel)
{
assert(ex_polygons.size() == ex_polygons_bboxes.size());
if(std::any_of(travel.points.begin(), travel.points.end(), [&grid_lslice](const Point &point) { return !grid_lslice.bbox().contains(point); }))
if(std::any_of(travel.points.begin(), travel.points.end(), [&grid_lslice_offset](const Point &point) { return !grid_lslice_offset.bbox().contains(point); }))
return false;
FirstIntersectionVisitor visitor(grid_lslice);
FirstIntersectionVisitor visitor(grid_lslice_offset);
bool any_intersection = false;
for (size_t line_idx = 1; line_idx < travel.size(); ++line_idx) {
visitor.pt_current = &travel.points[line_idx - 1];
visitor.pt_next = &travel.points[line_idx];
grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
grid_lslice_offset.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
any_intersection = visitor.intersect;
if (any_intersection) break;
}
@@ -698,13 +735,13 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
}
static bool need_wipe(const GCode &gcodegen,
const EdgeGrid::Grid &grid_lslice,
const Line &original_travel,
const Polyline &result_travel,
const size_t intersection_count)
const ExPolygons &lslices_offset,
const std::vector<BoundingBox> &lslices_offset_bboxes,
const EdgeGrid::Grid &grid_lslices_offset,
const Line &original_travel,
const Polyline &result_travel,
const size_t intersection_count)
{
const ExPolygons &lslices = gcodegen.layer()->lslices;
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().extruder()->id()) > 0.;
bool wipe_needed = false;
@@ -714,16 +751,16 @@ static bool need_wipe(const GCode &gcodegen,
// The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test.
// If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes.
if (z_lift_enabled) {
if (any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, original_travel)) {
if (any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, original_travel)) {
// Check if original_travel and result_travel are not same.
// If both are the same, then it is possible to skip testing of result_travel
wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) &&
!any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel);
!any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, result_travel);
} else {
wipe_needed = true;
}
} else {
wipe_needed = !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel);
wipe_needed = !any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, result_travel);
}
}
@@ -945,55 +982,71 @@ static std::vector<float> contour_distance(const EdgeGrid::Grid &grid,
// ExPolygons are handled one by one so returned ExPolygons could intersect.
static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset)
{
double min_contour_width = 2. * offset + SCALED_EPSILON;
double search_radius = 2. * (offset + min_contour_width);
ExPolygons ex_poly_result = ex_polygons;
const std::vector<double> min_contour_width_values = {offset / 2., offset, 2. * offset + SCALED_EPSILON};
ExPolygons ex_poly_result = ex_polygons;
resample_expolygons(ex_poly_result, offset / 2, scaled<double>(0.5));
for (ExPolygon &ex_poly : ex_poly_result) {
BoundingBox bbox(get_extents(ex_poly));
bbox.offset(SCALED_EPSILON);
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.create(ex_poly, coord_t(0.7 * search_radius));
std::vector<std::vector<float>> ex_poly_distances;
precompute_expolygon_distances(ex_poly, ex_poly_distances);
// Filter out expolygons smaller than 0.1mm^2
if (Vec2d bbox_size = bbox.size().cast<double>(); bbox_size.x() * bbox_size.y() < Slic3r::sqr(scale_(0.1f)))
continue;
std::vector<std::vector<float>> offsets;
offsets.reserve(ex_poly.holes.size() + 1);
for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) {
const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1];
assert(poly.is_counter_clockwise() == (idx_contour == 0));
std::vector<float> distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius);
for (float &distance : distances) {
if (distance < min_contour_width)
distance = 0.f;
else if (distance > min_contour_width + 2. * offset)
distance = - float(offset);
else
distance = - (distance - float(min_contour_width)) / 2.f;
}
offsets.emplace_back(distances);
}
for (const double &min_contour_width : min_contour_width_values) {
const size_t min_contour_width_idx = &min_contour_width - &min_contour_width_values.front();
const double search_radius = 2. * (offset + min_contour_width);
ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets);
// If variable_offset_inner_ex produces empty result, then original ex_polygon is used
if (offset_ex_poly.size() == 1) {
ex_poly = std::move(offset_ex_poly.front());
} else if (offset_ex_poly.size() > 1) {
// fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces
// tiny artefacts polygons, so these artefacts are removed.
double max_area = offset_ex_poly.front().area();
size_t max_area_idx = 0;
for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) {
double area = offset_ex_poly[poly_idx].area();
if (max_area < area) {
max_area = area;
max_area_idx = poly_idx;
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.create(ex_poly, coord_t(0.7 * search_radius));
std::vector<std::vector<float>> ex_poly_distances;
precompute_expolygon_distances(ex_poly, ex_poly_distances);
std::vector<std::vector<float>> offsets;
offsets.reserve(ex_poly.holes.size() + 1);
for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) {
const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1];
assert(poly.is_counter_clockwise() == (idx_contour == 0));
std::vector<float> distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius);
for (float &distance : distances) {
if (distance < min_contour_width)
distance = 0.f;
else if (distance > min_contour_width + 2. * offset)
distance = -float(offset);
else
distance = -(distance - float(min_contour_width)) / 2.f;
}
offsets.emplace_back(distances);
}
ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets);
// If variable_offset_inner_ex produces empty result, then original ex_polygon is used
if (offset_ex_poly.size() == 1 && offset_ex_poly.front().holes.size() == ex_poly.holes.size()) {
ex_poly = std::move(offset_ex_poly.front());
break;
} else if ((min_contour_width_idx + 1) < min_contour_width_values.size()) {
continue; // Try the next round with bigger min_contour_width.
} else if (offset_ex_poly.size() == 1) {
ex_poly = std::move(offset_ex_poly.front());
break;
} else if (offset_ex_poly.size() > 1) {
// fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces
// tiny artefacts polygons, so these artefacts are removed.
double max_area = offset_ex_poly.front().area();
size_t max_area_idx = 0;
for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) {
double area = offset_ex_poly[poly_idx].area();
if (max_area < area) {
max_area = area;
max_area_idx = poly_idx;
}
}
ex_poly = std::move(offset_ex_poly[max_area_idx]);
break;
}
ex_poly = std::move(offset_ex_poly[max_area_idx]);
}
}
return ex_poly_result;
@@ -1133,10 +1186,8 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
Vec2d startf = start.cast<double>();
Vec2d endf = end .cast<double>();
const ExPolygons &lslices = gcodegen.layer()->lslices;
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
bool is_support_layer = (dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr);
if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) {
bool is_support_layer = dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr;
if (!use_external && (is_support_layer || (!m_lslices_offset.empty() && !any_expolygon_contains(m_lslices_offset, m_lslices_offset_bboxes, m_grid_lslices_offset, travel)))) {
// Initialize m_internal only when it is necessary.
if (m_internal.boundaries.empty())
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer())));
@@ -1186,7 +1237,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
} else if (max_detour_length_exceeded) {
*could_be_wipe_disabled = false;
} else
*could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count);
*could_be_wipe_disabled = !need_wipe(gcodegen, m_lslices_offset, m_lslices_offset_bboxes, m_grid_lslices_offset, travel, result_pl, travel_intersection_count);
return result_pl;
}
@@ -1197,13 +1248,21 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer)
{
m_internal.clear();
m_external.clear();
m_lslices_offset.clear();
m_lslices_offset_bboxes.clear();
float perimeter_offset = -get_external_perimeter_width(layer) / float(2.);
m_lslices_offset = offset_ex(layer.lslices, perimeter_offset);
m_lslices_offset_bboxes.reserve(m_lslices_offset.size());
for (const ExPolygon &ex_poly : m_lslices_offset)
m_lslices_offset_bboxes.emplace_back(get_extents(ex_poly));
BoundingBox bbox_slice(get_extents(layer.lslices));
bbox_slice.offset(SCALED_EPSILON);
m_grid_lslice.set_bbox(bbox_slice);
//FIXME 1mm grid?
m_grid_lslice.create(layer.lslices, coord_t(scale_(1.)));
m_grid_lslices_offset.set_bbox(bbox_slice);
m_grid_lslices_offset.create(m_lslices_offset, coord_t(scale_(1.)));
}
#if 0
@@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_AvoidCrossingPerimeters_hpp_
#define slic3r_AvoidCrossingPerimeters_hpp_
@@ -58,8 +62,11 @@ private:
// we enable it by default for the first travel move in print
bool m_disabled_once { true };
// Lslices offseted by half an external perimeter width. Used for detection if line or polyline is inside of any polygon.
ExPolygons m_lslices_offset;
std::vector<BoundingBox> m_lslices_offset_bboxes;
// Used for detection of line or polyline is inside of any polygon.
EdgeGrid::Grid m_grid_lslice;
EdgeGrid::Grid m_grid_lslices_offset;
// Store all needed data for travels inside object
Boundary m_internal;
// Store all needed data for travels outside object
+1 -2
View File
@@ -1038,8 +1038,7 @@ void WipeTower2::toolchange_Change(
// The toolchange Tn command will be inserted later, only in case that the user does
// not provide a custom toolchange gcode.
writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed.
//writer.append("[start_filament_gcode]\n");
writer.append("[filament_start_gcode]\n");
// writer.append("[filament_start_gcode]\n");
writer.flush_planner_queue();
+3 -3
View File
@@ -320,7 +320,7 @@ std::string GCodeWriter::reset_e(bool force)
return "";
if (m_extruder != nullptr) {
if (m_extruder->E() == 0. && ! force)
if (is_zero(m_extruder->E()) && ! force)
return "";
m_extruder->reset_E();
}
@@ -709,7 +709,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
length = 1;
std::string gcode;
if (double dE = m_extruder->retract(length, restart_extra); dE != 0) {
if (double dE = m_extruder->retract(length, restart_extra); !is_zero(dE)) {
if (this->config.use_firmware_retraction) {
gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n";
}
@@ -737,7 +737,7 @@ std::string GCodeWriter::unretract()
if (FLAVOR_IS(gcfMakerWare))
gcode = "M101 ; extruder on\n";
if (double dE = m_extruder->unretract(); dE != 0) {
if (double dE = m_extruder->unretract(); !is_zero(dE)) {
if (this->config.use_firmware_retraction) {
gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n";
gcode += this->reset_e();
+3 -2
View File
@@ -726,7 +726,7 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
static std::vector<std::string> s_Preset_print_options {
"layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold",
"seam_position", "staggered_inner_seams", "wall_infill_order", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern",
"infill_direction",
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern",
@@ -756,6 +756,7 @@ static std::vector<std::string> s_Preset_print_options {
"flush_into_infill", "flush_into_objects", "flush_into_support",
"tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter",
"tree_support_branch_diameter", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall",
"detect_narrow_internal_solid_infill",
"gcode_add_line_number", "enable_arc_fitting", "infill_combination", /*"adaptive_layer_height",*/
"support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed",
"initial_layer_infill_speed", "only_one_wall_top",
@@ -818,7 +819,7 @@ static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "change_filament_gcode",
"single_extruder_multi_material", "manual_filament_change", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
"default_print_profile", "inherits",
"silent_mode",
+30 -3
View File
@@ -1183,6 +1183,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionStrings { " " });
def = this->add("ensure_vertical_shell_thickness", coBool);
def->label = L("Ensure vertical shell thickness");
def->category = L("Strength");
def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness "
"(top+bottom solid layers)");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
auto def_top_fill_pattern = def = this->add("top_surface_pattern", coEnum);
def->label = L("Top surface pattern");
def->category = L("Strength");
@@ -3493,6 +3501,7 @@ def = this->add("filament_loading_speed", coFloats);
def = this->add("support_top_z_distance", coFloat);
//def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->label = L("Top Z distance");
def->min = 0;
def->category = L("Support");
def->tooltip = L("The z gap between the top support interface and object");
def->sidetext = L("mm");
@@ -3508,12 +3517,12 @@ def = this->add("filament_loading_speed", coFloats);
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
// BBS:MusangKing
def = this->add("support_bottom_z_distance", coFloat);
def->label = L("Bottom Z distance");
def->category = L("Support");
def->tooltip = L("The z gap between the bottom support interface and object");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
@@ -3962,6 +3971,15 @@ def = this->add("filament_loading_speed", coFloats);
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("change_extrusion_role_gcode", coString);
def->label = L("Change extrusion role G-code");
def->tooltip = L("This gcode is inserted when the extrusion role is changed");
def->multiline = true;
def->full_width = true;
def->height = 5;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("top_surface_line_width", coFloatOrPercent);
def->label = L("Top surface");
def->category = L("Quality");
@@ -4390,6 +4408,15 @@ def = this->add("filament_loading_speed", coFloats);
default: assert(false);
}
}
def = this->add("detect_narrow_internal_solid_infill", coBool);
def->label = L("Detect narrow internal solid infill");
def->category = L("Strength");
def->tooltip = L("This option will auto detect narrow internal solid infill area."
" If enabled, concentric pattern will be used for the area to speed printing up."
" Otherwise, rectilinear pattern is used defaultly.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
}
void PrintConfigDef::init_extruder_option_keys()
@@ -5194,8 +5221,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
"support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer",
"can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height",
"z_hop_type", "z_lift_type", "bed_temperature_difference",
"detect_narrow_internal_solid_infill", "ensure_vertical_shell_thickness","extruder_type",
"internal_bridge_support_thickness","ensure_vertical_shell_thickness","extruder_clearance_max_radius"
"extruder_type",
"internal_bridge_support_thickness","extruder_clearance_max_radius"
};
if (ignore.find(opt_key) != ignore.end()) {
+3 -1
View File
@@ -747,6 +747,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, tree_support_adaptive_layer_height))
((ConfigOptionBool, tree_support_auto_brim))
((ConfigOptionFloat, tree_support_brim_width))
((ConfigOptionBool, detect_narrow_internal_solid_infill))
// ((ConfigOptionBool, adaptive_layer_height))
((ConfigOptionFloat, support_bottom_interface_spacing))
((ConfigOptionEnum<PerimeterGeneratorType>, wall_generator))
@@ -797,6 +798,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, bridge_flow))
((ConfigOptionFloat, bridge_speed))
((ConfigOptionFloatOrPercent, internal_bridge_speed))
((ConfigOptionBool, ensure_vertical_shell_thickness))
((ConfigOptionEnum<InfillPattern>, top_surface_pattern))
((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern))
((ConfigOptionEnum<InfillPattern>, internal_solid_infill_pattern))
@@ -967,6 +969,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, single_extruder_multi_material_priming))
((ConfigOptionBool, wipe_tower_no_sparse_layers))
((ConfigOptionString, change_filament_gcode))
((ConfigOptionString, change_extrusion_role_gcode))
((ConfigOptionFloat, travel_speed))
((ConfigOptionFloat, travel_speed_z))
((ConfigOptionBool, silent_mode))
@@ -1121,7 +1124,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
// BBS: project filaments
((ConfigOptionFloats, filament_colour_new))
// BBS: not in any preset, calculated before slicing
((ConfigOptionBool, has_prime_tower))
((ConfigOptionFloat, nozzle_volume))
((ConfigOptionPoints, start_end_points))
((ConfigOptionEnum<TimelapseType>, timelapse_type))
+178 -1
View File
@@ -1057,6 +1057,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "solid_infill_filament"
|| opt_key == "sparse_infill_line_width"
|| opt_key == "infill_direction"
|| opt_key == "ensure_vertical_shell_thickness"
|| opt_key == "bridge_angle"
//BBS
|| opt_key == "bridge_density") {
@@ -1520,6 +1521,18 @@ void PrintObject::discover_vertical_shells()
if (top_bottom_surfaces_all_regions) {
// This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness
// is calculated over all materials.
// Is the "ensure vertical wall thickness" applicable to any region?
bool has_extra_layers = false;
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
const PrintRegionConfig &config = this->printing_region(region_id).config();
if (config.ensure_vertical_shell_thickness.value) {
has_extra_layers = true;
break;
}
}
if (! has_extra_layers)
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
return;
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
@@ -1589,6 +1602,11 @@ void PrintObject::discover_vertical_shells()
}
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
const PrintRegion &region = this->printing_region(region_id);
if (! region.config().ensure_vertical_shell_thickness.value)
// This region will be handled by discover_horizontal_shells().
continue;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
@@ -3109,7 +3127,166 @@ void PrintObject::discover_horizontal_shells()
surface.surface_type = type;
}
#endif
// The rest has already been performed by discover_vertical_shells().
// If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells().
if (region_config.ensure_vertical_shell_thickness.value)
continue;
coordf_t print_z = layer->print_z;
coordf_t bottom_z = layer->bottom_z();
for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) {
m_print->throw_if_canceled();
SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge;
int num_solid_layers = (type == stTop) ? region_config.top_shell_layers.value : region_config.bottom_shell_layers.value;
if (num_solid_layers == 0)
continue;
// Find slices of current type for current layer.
// Use slices instead of fill_surfaces, because they also include the perimeter area,
// which needs to be propagated in shells; we need to grow slices like we did for
// fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
// not work in some situations, as there won't be any grown region in the perimeter
// area (this was seen in a model where the top layer had one extra perimeter, thus
// its fill_surfaces were thinner than the lower layer's infill), however it's the best
// solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
// too much solid infill inside nearly-vertical slopes.
// Surfaces including the area of perimeters. Everything, that is visible from the top / bottom
// (not covered by a layer above / below).
// This does not contain the areas covered by perimeters!
Polygons solid;
for (const Surface &surface : layerm->slices.surfaces)
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
// Infill areas (slices without the perimeters).
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
if (solid.empty())
continue;
// Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom';
// Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking.
for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1;
(type == stTop) ?
(n >= 0 && (int(i) - n < num_solid_layers ||
print_z - m_layers[n]->print_z < region_config.top_shell_thickness.value - EPSILON)) :
(n < int(m_layers.size()) && (n - int(i) < num_solid_layers ||
m_layers[n]->bottom_z() - bottom_z < region_config.bottom_shell_thickness.value - EPSILON));
(type == stTop) ? -- n : ++ n)
{
// Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
// Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id];
// find intersection between neighbor and current layer's surfaces
// intersections have contours and holes
// we update $solid so that we limit the next neighbor layer to the areas that were
// found on this one - in other words, solid shells on one layer (for a given external surface)
// are always a subset of the shells found on the previous shell layer
// this approach allows for DWIM in hollow sloping vases, where we want bottom
// shells to be generated in the base but not in the walls (where there are many
// narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
// upper perimeter as an obstacle and shell will not be propagated to more upper layers
//FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work.
Polygons new_internal_solid;
{
Polygons internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid)
polygons_append(internal, to_polygons(surface.expolygon));
new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes);
}
if (new_internal_solid.empty()) {
// No internal solid needed on this layer. In order to decide whether to continue
// searching on the next neighbor (thus enforcing the configured number of solid
// layers, use different strategies according to configured infill density:
if (region_config.sparse_infill_density.value == 0) {
// If user expects the object to be void (for example a hollow sloping vase),
// don't continue the search. In this case, we only generate the external solid
// shell if the object would otherwise show a hole (gap between perimeters of
// the two layers), and internal solid shells are a subset of the shells found
// on each previous layer.
goto EXTERNAL;
} else {
// If we have internal infill, we can generate internal solid shells freely.
continue;
}
}
if (region_config.sparse_infill_density.value == 0) {
// if we're printing a hollow object we discard any solid shell thinner
// than a perimeter width, since it's probably just crossing a sloping wall
// and it's not wanted in a hollow print even if it would make sense when
// obeying the solid shell count option strictly (DWIM!)
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
Polygons too_narrow = diff(
new_internal_solid,
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5));
// Trim the regularized region by the original region.
if (! too_narrow.empty())
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
}
// make sure the new internal solid is wide enough, as it might get collapsed
// when spacing is added in Fill.pm
{
//FIXME Vojtech: Disable this and you will be sorry.
float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size
// we use a higher miterLimit here to handle areas with acute angles
// in those cases, the default miterLimit would cut the corner and we'd
// get a triangle in $too_narrow; if we grow it below then the shell
// would have a different shape from the external surface and we'd still
// have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff(
new_internal_solid,
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this
// additional area in the next shell too
// make sure our grown surfaces don't exceed the fill area
Polygons internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.is_internal() && !surface.is_bridge())
polygons_append(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid,
intersection(
expand(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we can't
// remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge
// is grown, and that little space is an internal solid shell so
// it triggers this too_narrow logic.)
internal));
// solid = new_internal_solid;
}
}
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces);
polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid)));
ExPolygons internal_solid = union_ex(new_internal_solid);
// assign new internal-solid surfaces to layer
neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid);
// subtract intersections from layer surfaces to get resulting internal surfaces
Polygons polygons_internal = to_polygons(std::move(internal_solid));
ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes);
// assign resulting internal surfaces to layer
neighbor_layerm->fill_surfaces.append(internal, stInternal);
polygons_append(polygons_internal, to_polygons(std::move(internal)));
// assign top and bottom surfaces to layer
backup.keep_types({ stTop, stBottom, stBottomBridge });
std::vector<SurfacesPtr> top_bottom_groups;
backup.group(&top_bottom_groups);
for (SurfacesPtr &group : top_bottom_groups)
neighbor_layerm->fill_surfaces.append(
diff_ex(group, polygons_internal),
// Use an existing surface as a template, it carries the bridge angle etc.
*group.front());
}
EXTERNAL:;
} // foreach type (stTop, stBottom, stBottomBridge)
} // for each layer
} // for each region
-2
View File
@@ -119,8 +119,6 @@ SlicingParameters SlicingParameters::create_from_config(
//BBS
params.gap_object_support = object_config.support_bottom_z_distance.value;
params.gap_support_object = object_config.support_top_z_distance.value;
if (params.gap_object_support <= 0)
params.gap_object_support = params.gap_support_object;
if (!print_config.independent_support_layer_height) {
params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height;
+2 -9
View File
@@ -528,14 +528,14 @@ CalibPressureAdvancePattern::CalibPressureAdvancePattern(
};
void CalibPressureAdvancePattern::generate_custom_gcodes(const DynamicPrintConfig &config,
bool is_bbl_machine,
bool is_bbl_machine,
Model &model,
const Vec3d &origin)
{
std::stringstream gcode;
gcode << "; start pressure advance pattern for layer\n";
refresh_setup(config, is_bbl_machine, model, origin);
refresh_setup(config, is_bbl_machine, model, origin);
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to start XY position");
gcode << m_writer.travel_to_z(height_first_layer(), "Move to start Z position");
@@ -686,8 +686,6 @@ void CalibPressureAdvancePattern::refresh_setup(const DynamicPrintConfig &config
m_config.apply(model.objects.front()->config.get(), true);
m_config.apply(model.objects.front()->volumes.front()->config.get(), true);
m_is_delta = (m_config.option<ConfigOptionPoints>("printable_area")->values.size() > 4);
_refresh_starting_point(model);
_refresh_writer(is_bbl_machine, model, origin);
}
@@ -702,11 +700,6 @@ void CalibPressureAdvancePattern::_refresh_starting_point(const Model &model)
m_starting_point = Vec3d(bbox.min.x(), bbox.max.y(), 0);
m_starting_point.y() += m_handle_spacing;
if (m_is_delta) {
m_starting_point.x() *= -1;
m_starting_point.y() -= (frame_size_y() / 2);
}
}
void CalibPressureAdvancePattern::_refresh_writer(bool is_bbl_machine, const Model &model, const Vec3d &origin)
-1
View File
@@ -294,7 +294,6 @@ private:
const Calib_Params &m_params;
GCodeWriter m_writer;
bool m_is_delta;
Vec3d m_starting_point;
bool m_is_start_point_fixed = false;
+6
View File
@@ -278,6 +278,12 @@ constexpr inline T sqr(T x)
return x * x;
}
template<typename Number> constexpr
inline bool is_zero(Number value)
{
return std::fabs(double(value)) < 1e-6;
}
template <typename T, typename Number>
constexpr inline T lerp(const T& a, const T& b, Number t)
{
+3 -1
View File
@@ -277,6 +277,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
sparse_infill_density == 0 &&
! config->opt_bool("enable_support") &&
config->opt_int("enforce_support_layers") == 0 &&
config->opt_bool("ensure_vertical_shell_thickness") &&
! config->opt_bool("detect_thin_wall") &&
! config->opt_bool("overhang_reverse") &&
config->opt_enum<TimelapseType>("timelapse_type") == TimelapseType::tlTraditional))
@@ -304,6 +305,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(0));
new_conf.set_key_value("enable_support", new ConfigOptionBool(false));
new_conf.set_key_value("enforce_support_layers", new ConfigOptionInt(0));
new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true));
new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false));
new_conf.set_key_value("overhang_reverse", new ConfigOptionBool(false));
new_conf.set_key_value("timelapse_type", new ConfigOptionEnum<TimelapseType>(tlTraditional));
@@ -524,7 +526,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
}
bool have_perimeters = config->opt_int("wall_loops") > 0;
for (auto el : { "extra_perimeters_on_overhangs", "detect_thin_wall", "detect_overhang_wall",
for (auto el : { "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall",
"seam_position", "staggered_inner_seams", "wall_infill_order", "outer_wall_line_width",
"inner_wall_speed", "outer_wall_speed", "small_perimeter_speed", "small_perimeter_threshold" })
toggle_field(el, have_perimeters);
+39 -33
View File
@@ -2122,16 +2122,15 @@ void GUI_App::init_app_config()
app_config = new AppConfig();
//app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer);
m_config_corrupted = false;
// load settings
m_app_conf_exists = app_config->exists();
if (m_app_conf_exists) {
std::string error = app_config->load();
if (!error.empty()) {
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
throw Slic3r::RuntimeError(
_u8L("OrcaSlicer configuration file may be corrupted and is not abled to be parsed."
"Please delete the file and try again.") +
"\n\n" + app_config->config_path() + "\n\n" + error);
// Orca: if the config file is corrupted, we will show a error dialog and create a default config file.
m_config_corrupted = true;
}
// Save orig_version here, so its empty if no app_config existed before this run.
m_last_config_version = app_config->orig_version();//parse_semver_from_ini(app_config->config_path());
@@ -2477,7 +2476,6 @@ bool GUI_App::on_init_inner()
#endif // __WXMSW__
preset_updater = new PresetUpdater();
#if orca_todo
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
if (this->plater_ != nullptr) {
// this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable);
@@ -2566,7 +2564,6 @@ bool GUI_App::on_init_inner()
});
dlg.ShowModal();
});
#endif
}
else {
#ifdef __WXMSW__
@@ -2750,6 +2747,7 @@ bool GUI_App::on_init_inner()
if (m_post_initialized && app_config->dirty())
app_config->save();
});
m_initialized = true;
@@ -2757,6 +2755,13 @@ bool GUI_App::on_init_inner()
flush_logs();
BOOST_LOG_TRIVIAL(info) << "finished the gui app init";
if (m_config_corrupted) {
m_config_corrupted = false;
show_error(nullptr,
_u8L(
"The OrcaSlicer configuration file may be corrupted and cannot be parsed.\nOrcaSlicer has attempted to recreate the "
"configuration file.\nPlease note, application settings will be lost, but printer profiles will not be affected."));
}
//BBS: delete splash screen
delete scrn;
return true;
@@ -4266,7 +4271,7 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
error);
})
.timeout_connect(1)
.on_complete([&](std::string body, unsigned http_status) {
.on_complete([this,by_user](std::string body, unsigned http_status) {
// Http response OK
if (http_status != 200)
return;
@@ -4292,30 +4297,28 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
std::string best_release_content;
std::string best_pre_content;
const std::regex reg_num("([0-9]+)");
for (auto json_version : root) {
std::string tag = json_version.second.get<std::string>("tag_name");
if (tag[0] == 'v')
tag.erase(0, 1);
for (std::regex_iterator it = std::sregex_iterator(tag.begin(), tag.end(), reg_num);
it != std::sregex_iterator(); ++it) {
}
Semver tag_version = get_version(tag, matcher);
if (current_version == tag_version)
i_am_pre = json_version.second.get<bool>("prerelease");
if (json_version.second.get<bool>("prerelease")) {
if (best_pre < tag_version) {
best_pre = tag_version;
best_pre_url = json_version.second.get<std::string>("html_url");
best_pre_content = json_version.second.get<std::string>("body");
best_pre.set_prerelease("Preview");
}
} else {
if (best_release < tag_version) {
best_release = tag_version;
best_release_url = json_version.second.get<std::string>("html_url");
best_release_content = json_version.second.get<std::string>("body");
}
}
std::string tag = root.get<std::string>("tag_name");
if (tag[0] == 'v')
tag.erase(0, 1);
for (std::regex_iterator it = std::sregex_iterator(tag.begin(), tag.end(), reg_num);
it != std::sregex_iterator(); ++it) {
}
Semver tag_version = get_version(tag, matcher);
if (current_version == tag_version)
i_am_pre = root.get<bool>("prerelease");
if (root.get<bool>("prerelease")) {
if (best_pre < tag_version) {
best_pre = tag_version;
best_pre_url = root.get<std::string>("html_url");
best_pre_content = root.get<std::string>("body");
best_pre.set_prerelease("Preview");
}
} else {
if (best_release < tag_version) {
best_release = tag_version;
best_release_url = root.get<std::string>("html_url");
best_release_content = root.get<std::string>("body");
}
}
// if release is more recent than beta, use release anyway
@@ -4325,8 +4328,11 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
best_pre_content = best_release_content;
}
// if we're the most recent, don't do anything
if ((i_am_pre ? best_pre : best_release) <= current_version)
if ((i_am_pre ? best_pre : best_release) <= current_version) {
if (by_user != 0)
this->no_new_version();
return;
}
// BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...",
// SLIC3R_APP_NAME, i_am_pre ? best_pre.to_string(): best_release.to_string());
+1
View File
@@ -656,6 +656,7 @@ private:
bool m_datadir_redefined { false };
std::string m_older_data_dir_path;
boost::optional<Semver> m_last_config_version;
bool m_config_corrupted { false };
};
DECLARE_APP(GUI_App)
+2
View File
@@ -37,6 +37,8 @@ protected:
std::string get_action_snapshot_name() override { return "Paint-on seam editing"; }
static const constexpr float CursorRadiusMin = 0.05f; // cannot be zero
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
private:
bool on_init() override;
+27 -16
View File
@@ -147,6 +147,17 @@ bool GLGizmoText::on_init()
reset_text_info();
m_desc["font"] = _L("Font");
m_desc["size"] = _L("Size");
m_desc["thickness"] = _L("Thickness");
m_desc["text_gap"] = _L("Text Gap");
m_desc["angle"] = _L("Angle");
m_desc["embeded_depth"] = _L("Embeded\ndepth");
m_desc["input_text"] = _L("Input text");
m_desc["surface"] = _L("Surface");
m_desc["horizontal_text"] = _L("Horizontal text");
m_desc["rotate_text_caption"] = _L("Shift + Mouse move up or dowm");
m_desc["rotate_text"] = _L("Rotate text");
@@ -669,13 +680,13 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale);
GizmoImguiBegin("Text", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
float space_size = m_imgui->get_style_scaling() * 8;
float font_cap = m_imgui->calc_text_size(_L("Font")).x;
float size_cap = m_imgui->calc_text_size(_L("Size")).x;
float thickness_cap = m_imgui->calc_text_size(_L("Thickness")).x;
float input_cap = m_imgui->calc_text_size(_L("Input text")).x;
float depth_cap = m_imgui->calc_text_size(_L("Embeded")).x;
float caption_size = std::max(std::max(font_cap, size_cap), std::max(depth_cap, input_cap)) + space_size + ImGui::GetStyle().WindowPadding.x;
const float space_size = m_imgui->get_style_scaling() * 8;
const std::array<std::string, 7> cap_array = std::array<std::string, 7>{ "font", "size", "thickness", "text_gap", "angle", "embeded_depth", "input_text" };
float caption_size = 0.0f;
for (const auto &t : cap_array) {
caption_size = std::max(caption_size, m_imgui->calc_text_size(m_desc[t]).x);
}
caption_size += space_size + ImGui::GetStyle().WindowPadding.x;
float input_text_size = m_imgui->scaled(10.0f);
float button_size = ImGui::GetFrameHeight();
@@ -702,7 +713,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Font"));
m_imgui->text(m_desc["font"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(list_width);
push_combo_style(currt_scale);
@@ -742,7 +753,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
ImGui::PopStyleVar(2);
pop_combo_style();
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Size"));
m_imgui->text(m_desc["size"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(input_size);
if(ImGui::InputFloat("###font_size", &m_font_size, 0.0f, 0.0f, "%.2f"))
@@ -769,7 +780,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
ImGui::PopStyleVar(3);
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Thickness"));
m_imgui->text(m_desc["thickness"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(list_width);
float old_value = m_thickness;
@@ -784,7 +795,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
const float drag_left_width = caption_size + slider_width + space_size;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Text Gap"));
m_imgui->text(m_desc["text_gap"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
if (m_imgui->bbl_slider_float_style("##text_gap", &m_text_gap, -100, 100, "%.2f", 1.0f, true))
@@ -795,7 +806,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
m_need_update_text = true;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Angle"));
m_imgui->text(m_desc["angle"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
if (m_imgui->bbl_slider_float_style("##angle", &m_rotate_angle, 0, 360, "%.2f", 1.0f, true))
@@ -806,7 +817,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
m_need_update_text = true;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Embeded\ndepth"));
m_imgui->text(m_desc["embeded_depth"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(list_width);
old_value = m_embeded_depth;
@@ -817,7 +828,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
m_need_update_text = true;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Input text"));
m_imgui->text(m_desc["input_text"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(list_width);
@@ -835,12 +846,12 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SameLine(caption_size);
ImGui::AlignTextToFramePadding();
if (m_imgui->bbl_checkbox(_L("Surface"), m_is_surface_text))
if (m_imgui->bbl_checkbox(m_desc["surface"], m_is_surface_text))
m_need_update_text = true;
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
if (m_imgui->bbl_checkbox(_L("Horizontal text"), m_keep_horizontal))
if (m_imgui->bbl_checkbox(m_desc["horizontal_text"], m_keep_horizontal))
m_need_update_text = true;
//ImGui::SameLine();
+1 -1
View File
@@ -16,7 +16,7 @@ constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2
static const float LEFT_MARGIN = 13.0f + 100.0f; // avoid thumbnail toolbar
static const float HORIZONTAL_SLIDER_WINDOW_HEIGHT = 64.0f;
static const float VERTICAL_SLIDER_WINDOW_WIDTH = 124.0f;
static const float VERTICAL_SLIDER_WINDOW_WIDTH = 160.0f;
static const float GROOVE_WIDTH = 12.0f;
static const ImVec2 ONE_LAYER_MARGIN = ImVec2(20.0f, 20.0f);
static const ImVec2 ONE_LAYER_BUTTON_SIZE = ImVec2(28.0f, 28.0f);
+1 -1
View File
@@ -459,7 +459,7 @@ void PartPlate::calc_vertex_for_icons(int index, GeometryBuffer &buffer)
ExPolygon poly;
auto bed_ext = get_extents(m_shape);
Vec2d p = bed_ext[2];
if (m_plater->get_build_volume_type() == BuildVolume_Type::Circle)
if (m_plater && m_plater->get_build_volume_type() == BuildVolume_Type::Circle)
p[1] -= std::max(
0.0, (bed_ext.size()(1) - 5 * PARTPLATE_ICON_SIZE - 4 * PARTPLATE_ICON_GAP_Y - PARTPLATE_ICON_GAP_TOP) / 2);
+7
View File
@@ -1056,6 +1056,13 @@ void Sidebar::update_all_preset_comboboxes()
m_bed_type_list->SelectAndNotify((int)bed_type - 1);
}
m_bed_type_list->Enable();
auto str_bed_type = wxGetApp().app_config->get_printer_setting(wxGetApp().preset_bundle->printers.get_selected_preset_name(), "curr_bed_type");
if(!str_bed_type.empty()){
int bed_type_value = atoi(str_bed_type.c_str());
if(bed_type_value == 0)
bed_type_value = 1;
m_bed_type_list->SelectAndNotify(bed_type_value - 1);
}
} else {
connection_btn->Show();
ams_btn->Hide();
+13
View File
@@ -1941,6 +1941,8 @@ void TabPrint::build()
optgroup->append_single_option_line("bridge_angle");
optgroup->append_single_option_line("minimum_sparse_infill_area");
optgroup->append_single_option_line("infill_combination");
optgroup->append_single_option_line("detect_narrow_internal_solid_infill");
optgroup->append_single_option_line("ensure_vertical_shell_thickness");
page = add_options_page(L("Speed"), "empty");
optgroup = page->new_optgroup(L("Initial layer speed"), L"param_speed_first", 15);
@@ -3229,6 +3231,17 @@ void TabPrinter::build_fff()
option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("Change extrusion role G-code"), L"param_gcode", 0);
optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup, opt_key, value);
};
option = optgroup->get_option("change_extrusion_role_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("Pause G-code"), L"param_gcode", 0);
optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup, opt_key, value);