Brim: rework the brim algorithm.

- now can extrude a brim for an island inside a hole
 - now can extrude "interior brim"
 - ditch the old complicated code that didn't use the ClipperUtils methods
This commit is contained in:
supermerill 2019-10-15 16:59:17 +02:00
parent 681bc59e5a
commit 1ffac85486
6 changed files with 276 additions and 186 deletions

View File

@ -1569,18 +1569,19 @@ void Print::process()
const Points copies = obj->copies();
obj->m_copies.clear();
obj->m_copies.emplace_back();
if (config().brim_ears)
this->_make_brim_ears({ obj }, obj->m_brim);
else
this->_make_brim({ obj }, obj->m_brim);
ExPolygons brim_area = (config().brim_ears)
? this->_make_brim_ears({ obj }, obj->m_brim)
: this->_make_brim({ obj }, obj->m_brim);
if (config().brim_width_interior > 0)
_make_brim_interior({ obj }, brim_area, obj->m_brim);
obj->m_copies = copies;
}
} else {
if (config().brim_ears)
this->_make_brim_ears(m_objects, m_brim);
else
this->_make_brim(m_objects, m_brim);
ExPolygons brim_area = (config().brim_ears)
? this->_make_brim_ears(m_objects, m_brim)
: this->_make_brim(m_objects, m_brim);
if (config().brim_width_interior > 0)
_make_brim_interior(m_objects, brim_area, m_brim);
}
}
this->set_done(psBrim);
@ -1759,207 +1760,187 @@ void Print::_make_skirt(const PrintObjectPtrs &objects, ExtrusionEntityCollectio
out.reverse();
}
void Print::_make_brim(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out) {
// Brim is only printed on first layer and uses perimeter extruder.
//TODO: test if no regression vs old _make_brim.
// this new one can extrude brim for an object inside an other object.
ExPolygons Print::_make_brim(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out) {
Flow flow = this->brim_flow();
Polygons islands;
ExPolygons islands;
for (PrintObject *object : objects) {
Polygons object_islands;
ExPolygons object_islands;
for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
object_islands.push_back(expoly.contour);
if (!object->support_layers().empty())
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
object_islands.push_back(expoly);
if (!object->support_layers().empty()) {
Polygons polys = object->support_layers().front()->support_fills.polygons_covered_by_spacing(float(SCALED_EPSILON));
for (Polygon poly : polys)
for (ExPolygon & expoly2 : union_ex(poly))
object_islands.emplace_back(expoly2);
}
islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
for (const Point &pt : object->m_copies)
for (Polygon &poly : object_islands) {
for (ExPolygon &poly : object_islands) {
islands.push_back(poly);
islands.back().translate(pt);
islands.back().translate(pt.x(), pt.y());
}
}
Polygons loops;
size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
this->throw_if_canceled();
//simplify & merge
ExPolygons unbrimmable_areas;
for (ExPolygon &expoly : islands)
for (ExPolygon &expoly : expoly.simplify(SCALED_RESOLUTION))
unbrimmable_areas.emplace_back(std::move(expoly));
islands = union_ex(unbrimmable_areas, true);
unbrimmable_areas = islands;
//get the brimmable area
const size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
ExPolygons brimmable_areas;
for (ExPolygon &expoly : islands) {
for (Polygon poly : offset(expoly.contour, num_loops * flow.scaled_width(), jtSquare)) {
brimmable_areas.emplace_back();
brimmable_areas.back().contour = poly;
brimmable_areas.back().contour.make_counter_clockwise();
brimmable_areas.back().holes.push_back(expoly.contour);
brimmable_areas.back().holes.back().make_clockwise();
}
}
brimmable_areas = union_ex(brimmable_areas);
this->throw_if_canceled();
//don't collide with objects
brimmable_areas = diff_ex(brimmable_areas, unbrimmable_areas, true);
//now get all holes, use them to create loops
Polylines loops;
ExPolygons bigger_islands;
//grow a half of spacing, to go to the first extrusion polyline.
Polygons unbrimmable_polygons;
for (ExPolygon &expoly : islands) {
unbrimmable_polygons.push_back(expoly.contour);
//do it separately because we don't want to union them
for (ExPolygon &big_expoly : offset_ex(expoly, double(flow.scaled_spacing()) * 0.5, jtSquare)) {
bigger_islands.emplace_back(big_expoly);
unbrimmable_polygons.insert(unbrimmable_polygons.end(), big_expoly.holes.begin(), big_expoly.holes.end());
}
}
//frontiers = to_polygons(union_ex(frontiers));
islands = bigger_islands;
for (size_t i = 0; i < num_loops; ++i) {
this->throw_if_canceled();
islands = offset(islands, double(flow.scaled_spacing()), jtSquare);
for (Polygon &poly : islands) {
// poly.simplify(SCALED_RESOLUTION);
poly.points.push_back(poly.points.front());
Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION);
p.pop_back();
poly.points = std::move(p);
// only grow the contour, not holes
bigger_islands.clear();
if (i > 0) {
for (ExPolygon &expoly : islands) {
for (Polygon &big_contour : offset(expoly.contour, double(flow.scaled_spacing()) * i, jtSquare)) {
bigger_islands.emplace_back(expoly);
bigger_islands.back().contour = big_contour;
}
polygons_append(loops, offset(islands, -0.5f * double(flow.scaled_spacing())));
}
loops = union_pt_chained(loops, false);
// The function above produces ordering well suited for concentric infill (from outside to inside).
// For Brim, the ordering should be reversed (from inside to outside).
std::reverse(loops.begin(), loops.end());
// If there is a possibility that brim intersects skirt, go through loops and split those extrusions
// The result is either the original Polygon or a list of Polylines
if (! m_skirt.empty() && m_config.skirt_distance.value < m_config.brim_width)
{
// Find the bounding polygons of the skirt
const Polygons skirt_inners = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.back())->polygon(),
-float(scale_(this->skirt_flow().spacing()))/2.f,
ClipperLib::jtRound,
float(scale_(0.1)));
const Polygons skirt_outers = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.front())->polygon(),
float(scale_(this->skirt_flow().spacing()))/2.f,
ClipperLib::jtRound,
float(scale_(0.1)));
// First calculate the trimming region.
ClipperLib_Z::Paths trimming;
{
ClipperLib_Z::Paths input_subject;
ClipperLib_Z::Paths input_clip;
for (const Polygon &poly : skirt_outers) {
input_subject.emplace_back();
ClipperLib_Z::Path &out = input_subject.back();
out.reserve(poly.points.size());
for (const Point &pt : poly.points)
out.emplace_back(pt.x(), pt.y(), 0);
}
for (const Polygon &poly : skirt_inners) {
input_clip.emplace_back();
ClipperLib_Z::Path &out = input_clip.back();
out.reserve(poly.points.size());
for (const Point &pt : poly.points)
out.emplace_back(pt.x(), pt.y(), 0);
else bigger_islands = islands;
bigger_islands = union_ex(bigger_islands);
for (ExPolygon &expoly : bigger_islands) {
loops.push_back(expoly.contour);
//also add hole, in case of it's merged with a contour.
for (Polygon &hole : expoly.holes)
//but remove the points that are inside the holes of islands
for(Polyline &pl : diff_pl(hole, unbrimmable_polygons, true))
loops.emplace_back(pl);
}
// init Clipper
ClipperLib_Z::Clipper clipper;
// add polygons
clipper.AddPaths(input_subject, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib_Z::ptClip, true);
// perform operation
clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
}
// Second, trim the extrusion loops with the trimming regions.
ClipperLib_Z::Paths loops_trimmed;
{
// Produce a closed polyline (repeat the first point at the end).
ClipperLib_Z::Paths input_clip;
for (const Polygon &loop : loops) {
input_clip.emplace_back();
ClipperLib_Z::Path& out = input_clip.back();
out.reserve(loop.points.size());
int64_t loop_idx = &loop - &loops.front();
for (const Point& pt : loop.points)
// The Z coordinate carries index of the source loop.
out.emplace_back(pt.x(), pt.y(), loop_idx + 1);
out.emplace_back(out.front());
}
// init Clipper
ClipperLib_Z::Clipper clipper;
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) {
// Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment
// hat the Z coordinate not set to the contour coordinate.
pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z));
});
// add polygons
clipper.AddPaths(input_clip, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(trimming, ClipperLib_Z::ptClip, true);
// perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
//intersection
Polygons frontiers;
//use contour from brimmable_areas (external frontier)
for (ExPolygon &expoly : brimmable_areas) {
frontiers.push_back(expoly.contour);
frontiers.back().make_counter_clockwise();
}
// add internal frontier
frontiers.insert(frontiers.begin(), unbrimmable_polygons.begin(), unbrimmable_polygons.end());
// Third, produce the extrusions, sorted by the source loop indices.
{
std::vector<std::pair<const ClipperLib_Z::Path*, size_t>> loops_trimmed_order;
loops_trimmed_order.reserve(loops_trimmed.size());
for (const ClipperLib_Z::Path &path : loops_trimmed) {
size_t input_idx = 0;
for (const ClipperLib_Z::IntPoint &pt : path)
if (pt.Z > 0) {
input_idx = (size_t)pt.Z;
break;
}
assert(input_idx != 0);
loops_trimmed_order.emplace_back(&path, input_idx);
}
std::stable_sort(loops_trimmed_order.begin(), loops_trimmed_order.end(),
[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
return l.second < r.second;
});
Vec3f last_pt(0.f, 0.f, 0.f);
Polylines lines = intersection_pl(loops, frontiers, true);
//{
// std::stringstream stri;
// stri << "exter_brim_test_" << ".svg";
// SVG svg(stri.str());
// svg.draw(unbrimmable_areas, "red");
// svg.draw(brimmable_areas, "green");
// svg.draw(to_polylines(frontiers), "blue");
// svg.draw((loops), "pink");
// svg.draw(lines, "yellow");
// svg.Close();
//}
for (size_t i = 0; i < loops_trimmed_order.size();) {
// Find all pieces that the initial loop was split into.
size_t j = i + 1;
for (; j < loops_trimmed_order.size() && loops_trimmed_order[i].first == loops_trimmed_order[j].first; ++ j) ;
const ClipperLib_Z::Path &first_path = *loops_trimmed_order[i].first;
if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
auto *loop = new ExtrusionLoop();
out.entities.emplace_back(loop);
loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
i = j;
} else {
//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance.
for (; i < j; ++ i) {
out.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast<ExtrusionPath*>(out.entities.back())->polyline.points;
points.reserve(path.size());
for (const ClipperLib_Z::IntPoint &pt : path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
}
}
}
}
} else {
extrusion_entities_append_loops(out.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
}
this->throw_if_canceled();
//reorder & extrude them
_extrude_brim_polyline(lines, out);
return brimmable_areas;
}
void Print::_make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out) {
ExPolygons Print::_make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out) {
Flow flow = this->brim_flow();
Points pt_ears;
Polygons islands;
ExPolygons islands;
for (PrintObject *object : objects) {
Polygons object_islands;
ExPolygons object_islands;
for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
object_islands.push_back(expoly.contour);
if (!object->support_layers().empty())
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
object_islands.push_back(expoly);
if (!object->support_layers().empty()) {
Polygons polys = object->support_layers().front()->support_fills.polygons_covered_by_spacing(float(SCALED_EPSILON));
for (Polygon poly : polys)
for (ExPolygon & expoly2 : union_ex(poly))
object_islands.push_back(expoly2);
}
islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
for (const Point &copy_pt : object->m_copies)
for (const Polygon &poly : object_islands) {
for (const ExPolygon &poly : object_islands) {
islands.push_back(poly);
islands.back().translate(copy_pt);
for (const Point &p : poly.convex_points(config().brim_ears_max_angle.value * PI / 180.0)) {
islands.back().translate(copy_pt.x(), copy_pt.y());
for (const Point &p : poly.contour.convex_points(config().brim_ears_max_angle.value * PI / 180.0)) {
pt_ears.push_back(p);
pt_ears.back() += (copy_pt);
}
}
}
islands = union_ex(islands, true);
//get the brimmable area (for the return value only)
const size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
ExPolygons brimmable_areas;
for (ExPolygon &expoly : islands) {
for (Polygon poly : offset(expoly.contour, num_loops * flow.scaled_width(), jtSquare)) {
brimmable_areas.emplace_back();
brimmable_areas.back().contour = poly;
brimmable_areas.back().contour.make_counter_clockwise();
brimmable_areas.back().holes.push_back(expoly.contour);
brimmable_areas.back().holes.back().make_clockwise();
}
}
brimmable_areas = union_ex(brimmable_areas);
//create loops (same as standard brim)
Polygons loops;
size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
islands = offset_ex(islands, -0.5f * double(flow.scaled_spacing()));
for (size_t i = 0; i < num_loops; ++i) {
this->throw_if_canceled();
islands = offset(islands, double(flow.scaled_spacing()), jtSquare);
for (Polygon &poly : islands) {
// poly.simplify(SCALED_RESOLUTION);
islands = offset_ex(islands, double(flow.scaled_spacing()), jtSquare);
for (ExPolygon &expoly : islands) {
Polygon poly = expoly.contour;
poly.points.push_back(poly.points.front());
Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION);
p.pop_back();
poly.points = std::move(p);
loops.push_back(poly);
}
polygons_append(loops, offset(islands, -0.5f * double(flow.scaled_spacing())));
}
loops = union_pt_chained(loops, false);
//create ear pattern
coord_t size_ear = (scale_(m_config.brim_width.value) - flow.scaled_spacing());
Polygon point_round;
@ -1970,14 +1951,107 @@ void Print::_make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityColle
//create ears
Polygons mouse_ears;
ExPolygons mouse_ears_ex;
for (Point pt : pt_ears) {
mouse_ears.push_back(point_round);
mouse_ears.back().translate(pt);
mouse_ears_ex.emplace_back();
mouse_ears_ex.back().contour = mouse_ears.back();
}
//intersection
Polylines lines = intersection_pl(loops, mouse_ears);
//reorder & extrude them
_extrude_brim_polyline(lines, out);
return intersection_ex(brimmable_areas, mouse_ears_ex);
}
ExPolygons Print::_make_brim_interior(const PrintObjectPtrs &objects, const ExPolygons &unbrimmable_areas, ExtrusionEntityCollection &out) {
// Brim is only printed on first layer and uses perimeter extruder.
Flow flow = this->brim_flow();
ExPolygons islands;
for (PrintObject *object : objects) {
ExPolygons object_islands;
for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
object_islands.push_back(expoly);
if (!object->support_layers().empty()) {
Polygons polys = object->support_layers().front()->support_fills.polygons_covered_by_spacing(float(SCALED_EPSILON));
for (Polygon poly : polys)
for (ExPolygon & expoly2 : union_ex(poly))
object_islands.push_back(expoly2);
}
islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
for (const Point &pt : object->m_copies)
for (ExPolygon &poly : object_islands) {
islands.push_back(poly);
islands.back().translate(pt.x(), pt.y());
}
}
islands = union_ex(islands);
//to have the brimmable areas, get all holes, use them as contour , add smaller hole inside and make a diff with unbrimmable
const size_t num_loops = size_t(floor(m_config.brim_width_interior.value / flow.spacing()));
ExPolygons brimmable_areas;
Polygons islands_to_loops;
for (const ExPolygon &expoly : islands) {
for (const Polygon &hole : expoly.holes) {
brimmable_areas.emplace_back();
brimmable_areas.back().contour = hole;
brimmable_areas.back().contour.make_counter_clockwise();
for (Polygon poly : offset(brimmable_areas.back().contour, -flow.scaled_width() * (double)num_loops, jtSquare)) {
brimmable_areas.back().holes.push_back(poly);
brimmable_areas.back().holes.back().make_clockwise();
}
islands_to_loops = brimmable_areas.back().contour;
}
}
brimmable_areas = diff_ex(brimmable_areas, islands, true);
brimmable_areas = diff_ex(brimmable_areas, unbrimmable_areas, true);
//now get all holes, use them to create loops
Polygons loops;
for (size_t i = 0; i < num_loops; ++i) {
this->throw_if_canceled();
islands_to_loops = offset(islands_to_loops, double(-flow.scaled_spacing()), jtSquare);
for (Polygon &poly : islands_to_loops) {
poly.points.push_back(poly.points.front());
Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION);
p.pop_back();
poly.points = std::move(p);
}
polygons_append(loops, offset(islands_to_loops, 0.5f * double(flow.scaled_spacing())));
}
loops = union_pt_chained(loops, false);
//intersection
Polygons frontiers;
for (ExPolygon &expoly : brimmable_areas) {
for (Polygon &big_contour : offset(expoly.contour, 0.1f * flow.scaled_width())) {
frontiers.push_back(big_contour);
for (Polygon &hole : expoly.holes) {
frontiers.push_back(hole);
//don't reverse it! back! or it will be ignored by intersection_pl.
//frontiers.back().reverse();
}
}
}
Polylines lines = intersection_pl(loops, frontiers);
//reorder & extrude them
_extrude_brim_polyline(lines, out);
return brimmable_areas;
}
/// reorder & join polyline if their ending are near enough, then extrude the brim from the polyline into 'out'.
void Print::_extrude_brim_polyline(Polylines lines, ExtrusionEntityCollection &out) {
Flow flow = this->brim_flow();
//reorder them
std::sort(lines.begin(), lines.end(), [](const Polyline &a, const Polyline &b)->bool { return a.closest_point(Point(0, 0))->y() < b.closest_point(Point(0, 0))->y(); });
Polylines lines_sorted;
@ -2043,7 +2117,6 @@ void Print::_make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityColle
//update last position
previous = &lines_sorted.back();
}
}
}

View File

@ -394,8 +394,10 @@ private:
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
void _make_skirt(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out);
void _make_brim(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out);
void _make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out);
ExPolygons _make_brim(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out);
ExPolygons _make_brim_ears(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out);
ExPolygons _make_brim_interior(const PrintObjectPtrs &objects, const ExPolygons &unbrimmable, ExtrusionEntityCollection &out);
void _extrude_brim_polyline(Polylines lines, ExtrusionEntityCollection &out);
void _make_wipe_tower();
void _simplify_slices(double distance);

View File

@ -248,6 +248,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("brim_width_interior", coFloat);
def->label = L("Interior Brim width");
def->tooltip = L("Horizontal width of the brim that will be printed inside each object on the first layer.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("brim_ears", coBool);
def->label = L(" ");
def->full_label = L("Brim ears");

View File

@ -943,6 +943,7 @@ public:
ConfigOptionFloat bridge_acceleration;
ConfigOptionInts bridge_fan_speed;
ConfigOptionFloat brim_width;
ConfigOptionFloat brim_width_interior;
ConfigOptionBool brim_ears;
ConfigOptionFloat brim_ears_max_angle;
ConfigOptionInts chamber_temperature;
@ -1019,6 +1020,7 @@ protected:
OPT_PTR(bridge_acceleration);
OPT_PTR(bridge_fan_speed);
OPT_PTR(brim_width);
OPT_PTR(brim_width_interior);
OPT_PTR(brim_ears);
OPT_PTR(brim_ears_max_angle);
OPT_PTR(chamber_temperature);

View File

@ -402,7 +402,11 @@ const std::vector<std::string>& Preset::print_options()
"gap_fill_speed",
"travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
"min_skirt_length", "brim_width", "brim_ears", "brim_ears_max_angle",
"min_skirt_length",
"brim_width",
"brim_width_interior",
"brim_ears",
"brim_ears_max_angle",
"support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_interface_pattern",

View File

@ -877,7 +877,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key == "brim_width")
{
bool val = m_config->opt_float("brim_width") > 0.0 ? true : false;
bool val = m_config->opt_float("brim_width") > 0.0 || m_config->opt_float("brim_width_interior");
og_freq_chng_params->set_value("brim", val);
}
@ -1020,9 +1020,9 @@ void Tab::update_frequently_changed_parameters()
if (is_fff)
{
og_freq_chng_params->set_value("brim", bool(m_config->opt_float("brim_width") > 0.0));
og_freq_chng_params->set_value("brim", bool(m_config->opt_float("brim_width") > 0.0 || m_config->opt_float("brim_width_interior") > 0.0));
update_wiping_button_visibility();
}
}
}
void TabPrint::build()
@ -1156,6 +1156,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(_(L("Brim")));
optgroup->append_single_option_line("brim_width");
optgroup->append_single_option_line("brim_width_interior");
line = { _(L("Brim ears")), "" };
line.append_option(optgroup->get_option("brim_ears"));
line.append_option(optgroup->get_option("brim_ears_max_angle"));