Modify dense_infill to an automatic parameter

This commit is contained in:
supermerill 2018-08-03 19:54:19 +02:00 committed by supermerill
parent 05414e0e56
commit 5dd709bf1e
11 changed files with 210 additions and 167 deletions

View File

@ -59,7 +59,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
layerm.fill_surfaces.group(&groups);
//if internal infill can be dense, place it on his own group
if (layerm.region()->config.infill_dense_layers.getInt() > 0) {
if (layerm.region()->config.infill_dense.getBool() && layerm.region()->config.fill_density<40) {
SurfacesPtr *denseGroup = NULL;
const uint32_t nbGroups = groups.size();
for (uint32_t num_group = 0; num_group < nbGroups; ++num_group) {
@ -68,7 +68,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
//if solid, wrong group
if (srf->is_solid()) break;
//find surface that can be used as dense infill
if (srf->maxNbSolidLayersOnTop <= layerm.region()->config.infill_dense_layers.getInt()
if (srf->maxNbSolidLayersOnTop <= 1
&& srf->maxNbSolidLayersOnTop > 0) {
//remove from the group
groups[num_group].erase(groups[num_group].begin() + num_srf);
@ -193,12 +193,13 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
(surface.is_top() ? layerm.region()->config.top_fill_pattern.value : layerm.region()->config.bottom_fill_pattern.value) :
ipRectilinear;
} else {
if (layerm.region()->config.infill_dense_layers.getInt() > 0
&& surface.maxNbSolidLayersOnTop <= layerm.region()->config.infill_dense_layers.getInt()
if (layerm.region()->config.infill_dense.getBool()
&& layerm.region()->config.fill_density<40
&& surface.maxNbSolidLayersOnTop <= 1
&& surface.maxNbSolidLayersOnTop > 0) {
density = layerm.region()->config.infill_dense_density.getFloat();
density = 42;
is_denser = true;
fill_pattern = layerm.region()->config.infill_dense_pattern.value;
fill_pattern = ipRectiWithPerimeter;
}
if (density <= 0)
continue;
@ -253,7 +254,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
f->layer_id = layerm.layer()->id();
f->z = layerm.layer()->print_z;
if (is_denser)f->angle = float(Geometry::deg2rad(layerm.region()->config.infill_dense_angle.value));
if (is_denser)f->angle = 0;
else f->angle = float(Geometry::deg2rad(layerm.region()->config.fill_angle.value));
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = scale_(link_max_length);

View File

@ -39,6 +39,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipSmooth: return new FillSmooth();
case ipSmoothTriple: return new FillSmoothTriple();
case ipSmoothHilbert: return new FillSmoothHilbert();
case ipRectiWithPerimeter: return new FillRectilinear2Peri();
default: CONFESS("unknown type"); return nullptr;
}
}

View File

@ -1472,4 +1472,30 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &para
return polylines_out;
}
Polylines FillRectilinear2Peri::fill_surface(const Surface *surface, const FillParams &params) {
Polylines polylines_out;
//generate perimeter:
//TODO: better optimize start/end point?
ExPolygons path_perimeter = offset_ex(surface->expolygon, scale_(-this->spacing/2));
for (ExPolygon &expolygon : path_perimeter) {
expolygon.contour.make_counter_clockwise();
polylines_out.push_back(expolygon.contour.split_at_index(0));
for (Polygon hole : expolygon.holes) {
hole.make_clockwise();
polylines_out.push_back(hole.split_at_index(0));
}
}
//50% overlap with the new perimeter
ExPolygons path_inner = offset2_ex(surface->expolygon, scale_(-this->spacing * 1.5), scale_(this->spacing));
for (ExPolygon &expolygon : path_inner) {
Surface surfInner(*surface, expolygon);
if (!fill_surface_by_lines(&surfInner, params, 0.f, 0.f, polylines_out)) {
printf("FillRectilinear2::fill_surface() failed to fill a region.\n");
}
}
return polylines_out;
}
} // namespace Slic3r

View File

@ -68,6 +68,16 @@ protected:
virtual float _layer_angle(size_t idx) const { return 0.f; }
};
class FillRectilinear2Peri : public FillRectilinear2 {
public:
// require bridge flow since it's a pre-bridge over very sparse infill
virtual bool use_bridge_flow() const { return true; }
virtual Fill* clone() const { return new FillRectilinear2Peri(*this); };
virtual ~FillRectilinear2Peri() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
};
}; // namespace Slic3r

View File

@ -357,7 +357,7 @@ PrintConfigDef::PrintConfigDef()
def->sidetext = L("mm");
def->cli = "top-layer-anchor=f";
def->min = 0;
def->default_value = new ConfigOptionFloat(3);
def->default_value = new ConfigOptionFloat(1.5);
def = this->add("bridged_infill_margin", coFloat);
def->label = L("Bridged");
@ -366,7 +366,7 @@ PrintConfigDef::PrintConfigDef()
def->sidetext = L("mm");
def->cli = "top-layer-anchor=f";
def->min = 0;
def->default_value = new ConfigOptionFloat(3);
def->default_value = new ConfigOptionFloat(2);
def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
def->label = L("External perimeters");
@ -870,95 +870,12 @@ PrintConfigDef::PrintConfigDef()
def->min = 1;
def->default_value = new ConfigOptionInt(1);
def = this->add("infill_dense_layers", coInt);
def->label = L("Number of dense layers");
def = this->add("infill_dense", coBool);
def->label = L("Suporting dense layer");
def->category = L("Infill");
def->tooltip = L("Set the number of denser infill layer you want between the normal sparse infill and the top layers. 0 to disable");
def->sidetext = L("layers");
def->cli = "infill-dense-layers=i";
def->min = 0;
def->default_value = new ConfigOptionInt(0);
def = this->add("infill_dense_angle", coFloat);
def->label = L("angle");
def->category = L("Infill");
def->tooltip = L("Set the Angle of dense infill.");
def->sidetext = L("layers");
def->cli = "infill-dense-angle=i";
def->min = 0;
def->default_value = new ConfigOptionFloat(0);
def = this->add("infill_dense_density", coPercent);
def->gui_type = "f_enum_open";
def->gui_flags = "show_value";
def->label = L("Dense fill density");
def->category = L("Infill");
def->tooltip = L("Density of the dense internal infill, expressed in the range 0% - 100%.");
def->sidetext = L("%");
def->cli = "infill-dense-density=s";
def->min = 0;
def->max = 100;
def->enum_values.push_back("0");
def->enum_values.push_back("4");
def->enum_values.push_back("5.5");
def->enum_values.push_back("7.5");
def->enum_values.push_back("10");
def->enum_values.push_back("13");
def->enum_values.push_back("18");
def->enum_values.push_back("23");
def->enum_values.push_back("31");
def->enum_values.push_back("42");
def->enum_values.push_back("55");
def->enum_values.push_back("75");
def->enum_values.push_back("100");
def->enum_labels.push_back("0");
def->enum_labels.push_back("4");
def->enum_labels.push_back("5.5");
def->enum_labels.push_back("7.5");
def->enum_labels.push_back("10");
def->enum_labels.push_back("13");
def->enum_labels.push_back("18");
def->enum_labels.push_back("23");
def->enum_labels.push_back("31");
def->enum_labels.push_back("42");
def->enum_labels.push_back("55");
def->enum_labels.push_back("75");
def->enum_labels.push_back("100");
def->default_value = new ConfigOptionPercent(42);
def = this->add("infill_dense_pattern", coEnum);
def->label = L("pattern");
def->category = L("Sparse fill pattern");
def->tooltip = L("Fill pattern for denser-density sparse infill.");
def->cli = "dense-fill-pattern=s";
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("grid");
def->enum_values.push_back("triangles");
def->enum_values.push_back("stars");
def->enum_values.push_back("cubic");
def->enum_values.push_back("line");
def->enum_values.push_back("concentric");
def->enum_values.push_back("honeycomb");
def->enum_values.push_back("3dhoneycomb");
def->enum_values.push_back("gyroid");
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral");
def->enum_labels.push_back("Rectilinear");
def->enum_labels.push_back("Grid");
def->enum_labels.push_back("Triangles");
def->enum_labels.push_back("Stars");
def->enum_labels.push_back("Cubic");
def->enum_labels.push_back("Line");
def->enum_labels.push_back("Concentric");
def->enum_labels.push_back("Honeycomb");
def->enum_labels.push_back("3D Honeycomb");
def->enum_labels.push_back("Gyroid");
def->enum_labels.push_back("Hilbert Curve");
def->enum_labels.push_back("Archimedean Chords");
def->enum_labels.push_back("Octagram Spiral");
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
def->tooltip = L("Enable the creation of a support layer under the first solid layer. Allow to use lower infill ratio without compromizing the top quality");
def->cli = "infill-dense!";
def->default_value = new ConfigOptionBool(1);
def = this->add("infill_extruder", coInt);
def->label = L("Infill extruder");
@ -2446,11 +2363,6 @@ std::string FullPrintConfig::validate()
if (! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->bottom_fill_pattern.serialize()))
return "Invalid value for --bottom-fill-pattern";
// --infill-dense-pattern
if (! print_config_def.get("infill_dense_pattern")->has_enum_value(this->infill_dense_pattern.serialize()))
return "Invalid value for --infill-dense-pattern";
// --fill-density
if (fabs(this->fill_density.value - 100.) < EPSILON &&
(! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())

View File

@ -30,6 +30,7 @@ enum GCodeFlavor {
enum InfillPattern {
ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSmooth, ipSmoothHilbert, ipSmoothTriple,
ipRectiWithPerimeter,
};
enum SupportMaterialPattern {
@ -80,6 +81,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enu
keys_map["smooth"] = ipSmooth;
keys_map["smoothtriple"] = ipSmoothTriple;
keys_map["smoothhilbert"] = ipSmoothHilbert;
keys_map["rectiwithperimeter"] = ipRectiWithPerimeter;
}
return keys_map;
}
@ -412,10 +414,7 @@ public:
ConfigOptionInt infill_every_layers;
ConfigOptionFloatOrPercent infill_overlap;
ConfigOptionFloat infill_speed;
ConfigOptionInt infill_dense_layers;
ConfigOptionFloat infill_dense_angle;
ConfigOptionPercent infill_dense_density;
ConfigOptionEnum<InfillPattern> infill_dense_pattern;
ConfigOptionBool infill_dense;
ConfigOptionBool infill_first;
ConfigOptionBool overhangs;
ConfigOptionBool no_perimeter_unsupported;
@ -465,10 +464,7 @@ protected:
OPT_PTR(infill_every_layers);
OPT_PTR(infill_overlap);
OPT_PTR(infill_speed);
OPT_PTR(infill_dense_layers);
OPT_PTR(infill_dense_angle);
OPT_PTR(infill_dense_density);
OPT_PTR(infill_dense_pattern);
OPT_PTR(infill_dense);
OPT_PTR(infill_first);
OPT_PTR(overhangs);
OPT_PTR(no_perimeter_unsupported);

View File

@ -170,6 +170,8 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "elefant_foot_compensation"
|| opt_key == "support_material_contact_distance"
|| opt_key == "xy_size_compensation"
|| opt_key == "external_infill_margin"
|| opt_key == "bridged_infill_margin"
|| opt_key == "hole_size_compensation") {
steps.emplace_back(posSlice);
} else if (
@ -197,15 +199,10 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "infill_only_where_needed"
|| opt_key == "infill_every_layers"
|| opt_key == "solid_infill_every_layers"
|| opt_key == "infill_dense_layers"
|| opt_key == "infill_dense_angle"
|| opt_key == "infill_dense_density"
|| opt_key == "infill_dense_pattern"
|| opt_key == "infill_dense"
|| opt_key == "bottom_solid_layers"
|| opt_key == "top_solid_layers"
|| opt_key == "solid_infill_below_area"
|| opt_key == "external_infill_margin"
|| opt_key == "bridged_infill_margin"
|| opt_key == "infill_extruder"
|| opt_key == "solid_infill_extruder"
|| opt_key == "infill_extrusion_width"
@ -414,13 +411,101 @@ void PrintObject::_prepare_infill()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
void PrintObject::count_distance_solid() {
// Function used by fit_to_size.
// It check if polygon_to_check can be decimated, using only point into allowedPoints and also cover polygon_to_cover
ExPolygon try_fit_to_size(ExPolygon polygon_to_cover, ExPolygon polygon_to_check, const ExPolygons &allowedPoints) {
ExPolygon polygon_reduced = polygon_to_check;
auto point = polygon_reduced.contour.points.begin();
bool has_del = false;
while (point != polygon_reduced.contour.points.end()) {
bool ok = false;
for (ExPolygon poly : allowedPoints) {
if (poly.contains_b(*point)) {
ok = true;
has_del = true;
break;
}
}
if (ok) ++point;
else polygon_reduced.contour.points.erase(point);
}
if (has_del) polygon_reduced.holes.clear();
return polygon_reduced;
}
// find one of the smallest polygon, growing polygon_to_check, only using point into allowedPoints and covering polygon_to_cover.
ExPolygons fit_to_size(ExPolygon polygon_to_cover, ExPolygon polygon_to_check, const ExPolygons &allowedPoints,
const ExPolygon &growing_area, const coord_t offset, float coverage) {
//grow the polygon_to_check enough to cover polygon_to_cover
float current_coverage = current_coverage;
coord_t previous_offset = 0;
coord_t current_offset = offset;
ExPolygon polygon_reduced = try_fit_to_size(polygon_to_cover, polygon_to_check, allowedPoints);
while (!diff_ex(polygon_to_cover, polygon_reduced).empty()){
//not enough, use a bigger offset
float percent_coverage = polygon_reduced.area() / growing_area.area();
float next_coverage = percent_coverage + (percent_coverage - current_coverage) * 4;
previous_offset = current_offset;
current_offset *= 2;
if (next_coverage < 0.1) current_offset *= 2;
//create the bigger polygon and test it
ExPolygons bigger_polygon = offset_ex(polygon_to_check, current_offset);
if (bigger_polygon.size() != 1) {
// Error, growing a single polygon result in many/no other => fallback to full coverage
return ExPolygons({ growing_area });
}
bigger_polygon = intersection_ex(bigger_polygon[0], growing_area);
if (bigger_polygon.size() != 1 || bigger_polygon[0].area() > growing_area.area()) {
// Growing too much => we can as well use the full coverage, in this case
return ExPolygons() = { growing_area };
}
polygon_reduced = try_fit_to_size(polygon_to_cover, bigger_polygon[0], allowedPoints);
}
//ok, we have a good one, now try to optimise (unless there are almost no growth)
if (current_offset > offset * 3){
//try to shrink
uint32_t nb_opti_max = 6;
for (uint32_t i = 0; i < nb_opti_max; ++i){
coord_t new_offset = (previous_offset + current_offset) / 2;
ExPolygons bigger_polygon = offset_ex(polygon_to_check, new_offset);
if (bigger_polygon.size() != 1) {
//Warn, growing a single polygon result in many/no other, use previous good result
break;
}
bigger_polygon = intersection_ex(bigger_polygon[0], growing_area);
if (bigger_polygon.size() != 1 || bigger_polygon[0].area() > growing_area.area()) {
//growing too much, use previous good result (imo, should not be possible to enter this branch)
break;
}
ExPolygon polygon_test = try_fit_to_size(polygon_to_cover, bigger_polygon[0], allowedPoints);
if (!diff_ex(polygon_to_cover, polygon_test).empty()){
//bad, not enough, use a bigger offset
previous_offset = new_offset;
}
else {
//good, we may now try a smaller offset
current_offset = new_offset;
polygon_reduced = polygon_test;
}
}
}
//return the area which cover the growing_area. Intersect it to retreive the holes.
return intersection_ex(polygon_reduced, growing_area);
}
void PrintObject::count_distance_solid() {
//if dense area * COEFF_SPLIT > sparse area then fill all with dense
// sparse area = layer's fill area - dense area
const float COEFF_SPLIT = .1;
const int NB_DENSE_LAYERS = 1;
for (int idx_region = 0; idx_region < this->_print->regions.size(); ++idx_region) {
//count how many surface there are on each one
LayerRegion *previousOne = NULL;
if (this->layers.size() > 1) previousOne = this->layers[this->layers.size() - 1]->get_region(idx_region);
if (previousOne != NULL && previousOne->region()->config.infill_dense_layers.getInt() > 0) {
if (previousOne != NULL && previousOne->region()->config.infill_dense.getBool() && previousOne->region()->config.fill_density<40) {
for (Surface &surf : previousOne->fill_surfaces.surfaces) {
if (surf.is_solid()) {
surf.maxNbSolidLayersOnTop = 0;
@ -442,22 +527,34 @@ void PrintObject::count_distance_solid() {
// upp.expolygon.overlaps(surf.expolygon) or surf.expolygon.overlaps(upp.expolygon)
ExPolygons intersect = intersection_ex(sparse_polys, ExPolygons() = { upp.expolygon }, true);
if (!intersect.empty()) {
uint16_t dist = (uint16_t)(upp.maxNbSolidLayersOnTop + 1);
if (dist <= layerm->region()->config.infill_dense_layers.getInt()) {
// it will be a dense infill, split the surface if needed
uint64_t area_intersect = 0;
double area_intersect = 0;
for (ExPolygon poly_inter : intersect) area_intersect += poly_inter.area();
//if it's in a dense area and the current surface isn't a dense one yet and the not-dense is too small.
if (surf.area() > area_intersect * 3 &&
surf.maxNbSolidLayersOnTop > layerm->region()->config.infill_dense_layers.getInt()) {
//like intersect.empty() but more resilient
if (area_intersect > layerm->flow(frInfill).scaled_width() * layerm->flow(frInfill).scaled_width() * 2){
uint16_t dist = (uint16_t)(upp.maxNbSolidLayersOnTop + 1);
if (dist <= NB_DENSE_LAYERS) {
// it will be a dense infill, split the surface if needed
//if the not-dense is too big to do a full dense and the current surface isn't a dense one yet.
if (surf.area() > area_intersect * COEFF_SPLIT &&
surf.maxNbSolidLayersOnTop > NB_DENSE_LAYERS) {
//split in two
if (dist == 1) {
//if just under the solid area, we can expand a bit
//remove too small sections and grew a bit to anchor it into the part
intersect = offset2_ex(intersect,
ExPolygons cover_intersect;
for (ExPolygon &expoly_tocover : intersect) {
ExPolygons temp = (fit_to_size(expoly_tocover, expoly_tocover,
diff_ex(offset_ex(layerm->fill_no_overlap_expolygons, layerm->flow(frInfill).scaled_width()),
offset_ex(layerm->fill_no_overlap_expolygons, -layerm->flow(frInfill).scaled_width())),
surf.expolygon,
4 * layerm->flow(frInfill).scaled_width(), 0.01));
cover_intersect.insert(cover_intersect.end(), temp.begin(), temp.end());
}
intersect = offset2_ex(cover_intersect,
-layerm->flow(frInfill).scaled_width(),
layerm->flow(frInfill).scaled_width() + scale_(layerm->region()->config.external_infill_margin));
} else {
layerm->flow(frInfill).scaled_width());// +scale_(expandby));
//layerm->region()->config.external_infill_margin));
}
else {
//just remove too small sections
intersect = offset2_ex(intersect,
-layerm->flow(frInfill).scaled_width(),
@ -475,21 +572,25 @@ void PrintObject::count_distance_solid() {
dense_polys.insert(dense_polys.end(), dense_surfaces.begin(), dense_surfaces.end());
dense_dist = std::min(dense_dist, dist);
}
} else {
}
else {
if (area_intersect < layerm->flow(frInfill).scaled_width() * layerm->flow(frInfill).scaled_width() * 2)
surf.maxNbSolidLayersOnTop = std::min(surf.maxNbSolidLayersOnTop, dist);
}
} else {
}
else {
surf.maxNbSolidLayersOnTop = std::min(surf.maxNbSolidLayersOnTop, dist);
}
}
}
}
//check if we need to split the surface
if (dense_dist != 30000) {
uint64_t area_dense = 0;
double area_dense = 0;
for (ExPolygon poly_inter : dense_polys) area_dense += poly_inter.area();
uint64_t area_sparse = 0;
double area_sparse = 0;
for (ExPolygon poly_inter : sparse_polys) area_sparse += poly_inter.area();
if (area_sparse > area_dense * 3) {
if (area_sparse > area_dense * COEFF_SPLIT) {
//split
dense_polys = union_ex(dense_polys);
for (ExPolygon dense_poly : dense_polys) {
@ -526,6 +627,7 @@ void PrintObject::count_distance_solid() {
}
}
// This function analyzes slices of a region (SurfaceCollection slices).
// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
// Initially all slices are of type stInternal.

View File

@ -596,7 +596,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
break;
case coEnum:{
if (opt_key.compare("top_fill_pattern") == 0 || opt_key.compare("bottom_fill_pattern") == 0 ||
opt_key.compare("fill_pattern") == 0 || opt_key.compare("infill_dense_pattern") == 0)
opt_key.compare("fill_pattern") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
else if (opt_key.compare("gcode_flavor") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));

View File

@ -450,7 +450,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
break;
case coEnum:{
if (opt_key.compare("top_fill_pattern") == 0 || opt_key.compare("bottom_fill_pattern") == 0 ||
opt_key.compare("fill_pattern") == 0 || opt_key.compare("infill_dense_pattern") == 0 ){
opt_key.compare("fill_pattern") == 0 ){
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
}
else if (opt_key.compare("gcode_flavor") == 0 ){

View File

@ -306,7 +306,7 @@ const std::vector<std::string>& Preset::print_options()
"elefant_foot_compensation", "xy_size_compensation", "hole_size_compensation", "threads", "resolution",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging",
"only_one_perimeter_top", "compatible_printers", "compatible_printers_condition", "inherits",
"infill_dense_layers", "infill_dense_density", "infill_dense_pattern", "infill_dense_angle",
"infill_dense",
"no_perimeter_unsupported", "min_perimeter_unsupported", "noperi_bridge_only"
};
return s_opts;

View File

@ -833,12 +833,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(_(L("Reducing printing time")));
optgroup->append_single_option_line("infill_every_layers");
optgroup->append_single_option_line("infill_only_where_needed");
line = { _(L("Use denser infill below solid layers")), "" };
line.append_option(optgroup->get_option("infill_dense_layers"));
line.append_option(optgroup->get_option("infill_dense_density"));
line.append_option(optgroup->get_option("infill_dense_pattern"));
line.append_option(optgroup->get_option("infill_dense_angle"));
optgroup->append_line(line);
optgroup->append_single_option_line("infill_dense");
optgroup = page->new_optgroup(_(L("Advanced")));
optgroup->append_single_option_line("solid_infill_every_layers");