mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-07-14 23:51:57 +08:00
add "vase mode / no seam" option for external perimeters.
This commit is contained in:
parent
7854bed9c4
commit
9ae142ef3e
@ -2264,19 +2264,9 @@ std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std:
|
||||
return angles;
|
||||
}
|
||||
|
||||
std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::string &description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
|
||||
//like extrude_loop but with varying z and two full round
|
||||
std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const std::string &description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
|
||||
{
|
||||
#if DEBUG_EXTRUSION_OUTPUT
|
||||
std::cout << "extrude loop_" << (original_loop.polygon().is_counter_clockwise() ? "ccw" : "clw") << ": ";
|
||||
for (const ExtrusionPath &path : original_loop.paths) {
|
||||
std::cout << ", path{ ";
|
||||
for (const Point &pt : path.polyline.points) {
|
||||
std::cout << ", " << floor(100 * unscale<double>(pt.x())) / 100.0 << ":" << floor(100 * unscale<double>(pt.y())) / 100.0;
|
||||
}
|
||||
std::cout << "}";
|
||||
}
|
||||
std::cout << "\n";
|
||||
#endif
|
||||
// get a copy; don't modify the orientation of the original loop object otherwise
|
||||
// next copies (if any) would not detect the correct orientation
|
||||
ExtrusionLoop loop = original_loop;
|
||||
@ -2304,12 +2294,174 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
|
||||
|
||||
// extrude all loops ccw
|
||||
//no! this was decided in perimeter_generator
|
||||
bool was_clockwise = false;// loop.make_counter_clockwise();
|
||||
//if spiral vase, we have to ensure that all loops are in the same orientation.
|
||||
if (this->m_config.spiral_vase) {
|
||||
was_clockwise = loop.make_counter_clockwise();
|
||||
bool was_cw_and_now_ccw = false;// loop.make_counter_clockwise();
|
||||
|
||||
split_at_seam_pos(loop, lower_layer_edge_grid);
|
||||
|
||||
// clip the path to avoid the extruder to get exactly on the first point of the loop;
|
||||
// if polyline was shorter than the clipping distance we'd get a null polyline, so
|
||||
// we discard it in that case
|
||||
double clip_length = m_enable_loop_clipping ?
|
||||
scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
|
||||
0;
|
||||
|
||||
// get paths
|
||||
ExtrusionPaths paths;
|
||||
loop.clip_end(clip_length, &paths);
|
||||
if (paths.empty()) return "";
|
||||
|
||||
// apply the small perimeter speed
|
||||
if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
|
||||
speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
|
||||
|
||||
//get extrusion length
|
||||
coordf_t length = 0;
|
||||
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
|
||||
//path->simplify(SCALED_RESOLUTION); //not useful, this should have been done before.
|
||||
length += path->length() * SCALING_FACTOR;
|
||||
}
|
||||
|
||||
//all in unscaled coordinates (hence why it's coordf_t and not coord_t)
|
||||
const coordf_t min_height = EXTRUDER_CONFIG(min_layer_height);
|
||||
const coordf_t bot_init_z = - this->m_layer->height;
|
||||
//const coordf_t bot_last_z = bot_init_z + this->m_layer->height - EXTRUDER_CONFIG(min_layer_height);
|
||||
const coordf_t init_z = bot_init_z + min_height;
|
||||
//const coordf_t last_z = bot_init_z + this->m_layer->height;
|
||||
|
||||
coordf_t current_pos_in_length = 0;
|
||||
coordf_t current_z = init_z;
|
||||
coordf_t current_height = min_height;
|
||||
enum Step {
|
||||
INCR = 0,
|
||||
FLAT = 1
|
||||
};
|
||||
std::string gcode;
|
||||
for (int step = 0; step < 2; step++) {
|
||||
const coordf_t z_per_length = (step == Step::INCR) ? ((this->m_layer->height - (min_height + min_height)) / length) : 0;
|
||||
const coordf_t height_per_length = (step == Step::INCR) ? ((this->m_layer->height- (min_height + min_height)) / length) : ((-this->m_layer->height + (min_height + min_height)) / length);
|
||||
if (step == Step::FLAT) {
|
||||
current_z = 0;
|
||||
current_height = this->m_layer->height - min_height;
|
||||
}
|
||||
Vec3d previous;
|
||||
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
|
||||
|
||||
gcode += this->_before_extrude(*path, description, speed);
|
||||
|
||||
// calculate extrusion length per distance unit
|
||||
double e_per_mm_per_height = m_writer.extruder()->e_per_mm3() * path->mm3_per_mm / this->m_layer->height;
|
||||
if (m_writer.extrusion_axis().empty()) e_per_mm_per_height = 0;
|
||||
{
|
||||
std::string comment = m_config.gcode_comments ? description : "";
|
||||
for (const Line &line : path->polyline.lines()) {
|
||||
const coordf_t line_length = line.length() * SCALING_FACTOR;
|
||||
//don't go (much) more than a nozzle_size without a refresh of the z & extrusion rate
|
||||
const int nb_sections = std::max(1,int(line_length / EXTRUDER_CONFIG(nozzle_diameter)));
|
||||
const coordf_t height_increment = height_per_length * line_length / nb_sections;
|
||||
Vec3d last_point{ this->point_to_gcode(line.a).x(), this->point_to_gcode(line.a).y(), current_z };
|
||||
const Vec3d pos_increment{ (this->point_to_gcode(line.b).x() - last_point.x()) / nb_sections,
|
||||
(this->point_to_gcode(line.b).y() - last_point.y()) / nb_sections,
|
||||
z_per_length * line_length / nb_sections };
|
||||
coordf_t current_height_internal = current_height + height_increment / 2;
|
||||
//ensure you go to the good xyz
|
||||
if( (last_point - previous).norm() > EPSILON)
|
||||
gcode += m_writer.extrude_to_xyz(last_point, 0, description);
|
||||
//extrusions
|
||||
for (int i = 0; i < nb_sections - 1; i++) {
|
||||
Vec3d new_point = last_point + pos_increment;
|
||||
gcode += m_writer.extrude_to_xyz(new_point,
|
||||
e_per_mm_per_height * line_length * current_height_internal,
|
||||
description);
|
||||
current_height_internal += height_increment;
|
||||
}
|
||||
//last bit will go to the exact last pos
|
||||
last_point.x() = this->point_to_gcode(line.b).x();
|
||||
last_point.y() = this->point_to_gcode(line.b).y();
|
||||
last_point.z() = current_z + z_per_length * line_length;
|
||||
gcode += m_writer.extrude_to_xyz(
|
||||
last_point,
|
||||
e_per_mm_per_height * line_length * current_height_internal,
|
||||
comment);
|
||||
previous = last_point;
|
||||
|
||||
//update vars for next line
|
||||
current_pos_in_length += line_length;
|
||||
current_z = last_point.z();
|
||||
current_height += height_per_length * line_length;
|
||||
}
|
||||
}
|
||||
gcode += this->_after_extrude(*path);
|
||||
}
|
||||
}
|
||||
|
||||
// reset acceleration
|
||||
gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5));
|
||||
|
||||
//don't wipe here
|
||||
//if (m_wipe.enable)
|
||||
// m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path
|
||||
|
||||
//just continue on the perimeter a bit while retracting
|
||||
//FIXME this doesn't work work, hence why it's commented
|
||||
//coordf_t travel_length = std::min(length, EXTRUDER_CONFIG(nozzle_diameter) * 10);
|
||||
//for (auto & path : paths){
|
||||
// for (const Line &line : path.polyline.lines()) {
|
||||
// if (unscaled(line.length()) > travel_length) {
|
||||
// // generate the travel move
|
||||
// gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), "move inwards before travel");
|
||||
// travel_length -= unscaled(line.length());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// gcode += m_writer.travel_to_xy(this->point_to_gcode(line.a) + (this->point_to_gcode(line.b) - this->point_to_gcode(line.a)) * (travel_length / unscaled(line.length())), "move before travel");
|
||||
// travel_length = 0;
|
||||
// //double break;
|
||||
// goto FINISH_MOVE;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//FINISH_MOVE:
|
||||
|
||||
// make a little move inwards before leaving loop
|
||||
if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
|
||||
// detect angle between last and first segment
|
||||
// the side depends on the original winding order of the polygon (left for contours, right for holes)
|
||||
//FIXME improve the algorithm in case the loop is tiny.
|
||||
//FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
|
||||
Point a = paths.front().polyline.points[1]; // second point
|
||||
Point b = *(paths.back().polyline.points.end() - 3); // second to last point
|
||||
if (loop.polygon().is_clockwise()) {
|
||||
// swap points
|
||||
Point c = a; a = b; b = c;
|
||||
}
|
||||
|
||||
double angle = paths.front().first_point().ccw_angle(a, b) / 3;
|
||||
|
||||
// turn left if contour, turn right if hole
|
||||
if (loop.polygon().is_clockwise()) angle *= -1;
|
||||
|
||||
// create the destination point along the first segment and rotate it
|
||||
// we make sure we don't exceed the segment length because we don't know
|
||||
// the rotation of the second segment so we might cross the object boundary
|
||||
Vec2d p1 = paths.front().polyline.points.front().cast<double>();
|
||||
Vec2d p2 = paths.front().polyline.points[1].cast<double>();
|
||||
Vec2d v = p2 - p1;
|
||||
double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter));
|
||||
double l2 = v.squaredNorm();
|
||||
// Shift by no more than a nozzle diameter.
|
||||
//FIXME Hiding the seams will not work nicely for very densely discretized contours!
|
||||
Point pt = ((nd * nd >= l2) ? p2 : (p1 + v * (nd / sqrt(l2)))).cast<coord_t>();
|
||||
pt.rotate(angle, paths.front().polyline.points.front());
|
||||
// generate the travel move
|
||||
gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel");
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCode::split_at_seam_pos(ExtrusionLoop &loop, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
|
||||
{
|
||||
|
||||
SeamPosition seam_position = m_config.seam_position;
|
||||
if (loop.loop_role() == elrSkirt)
|
||||
seam_position = spNearest;
|
||||
@ -2372,8 +2524,8 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
|
||||
//TODO: ignore the angle penalty if the new point is not in an external path (bot/top/ext_peri)
|
||||
for (size_t i = 0; i < polygon.points.size(); ++i) {
|
||||
float ccwAngle = penalties[i];
|
||||
if (was_clockwise)
|
||||
ccwAngle = - ccwAngle;
|
||||
//if (was_cw_and_now_ccw) //FIXME
|
||||
//ccwAngle = -ccwAngle;
|
||||
float penalty = 0;
|
||||
// if (ccwAngle <- float(PI/3.))
|
||||
if (ccwAngle <-float(0.6 * PI))
|
||||
@ -2448,28 +2600,6 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
|
||||
m_seam_position[m_layer->object()] = polygon.points[idx_min];
|
||||
}
|
||||
|
||||
// Export the contour into a SVG file.
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
|
||||
if (m_layer->lower_layer != NULL)
|
||||
svg.draw(m_layer->lower_layer->slices.expolygons);
|
||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||
svg.draw(loop.paths[i].as_polyline(), "red");
|
||||
Polylines polylines;
|
||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||
polylines.push_back(loop.paths[i].as_polyline());
|
||||
Slic3r::Polygons polygons;
|
||||
coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
|
||||
coord_t delta = scale_(0.5*nozzle_dmr);
|
||||
Slic3r::offset(polylines, &polygons, delta);
|
||||
// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
|
||||
svg.draw(last_pos, "green", 3);
|
||||
svg.draw(polygon.points[idx_min], "yellow", 3);
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Split the loop at the point with a minium penalty.
|
||||
if (!loop.split_at_vertex(polygon.points[idx_min]))
|
||||
@ -2490,7 +2620,66 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
|
||||
}
|
||||
// Find the closest point, avoid overhangs.
|
||||
loop.split_at(last_pos, true);
|
||||
} else {
|
||||
loop.split_at(last_pos, false);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::string &description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
|
||||
{
|
||||
#if DEBUG_EXTRUSION_OUTPUT
|
||||
std::cout << "extrude loop_" << (original_loop.polygon().is_counter_clockwise() ? "ccw" : "clw") << ": ";
|
||||
for (const ExtrusionPath &path : original_loop.paths) {
|
||||
std::cout << ", path{ ";
|
||||
for (const Point &pt : path.polyline.points) {
|
||||
std::cout << ", " << floor(100 * unscale<double>(pt.x())) / 100.0 << ":" << floor(100 * unscale<double>(pt.y())) / 100.0;
|
||||
}
|
||||
std::cout << "}";
|
||||
}
|
||||
std::cout << "\n";
|
||||
#endif
|
||||
|
||||
//no-seam code path redirect
|
||||
if (original_loop.role() == ExtrusionRole::erExternalPerimeter && this->m_config.external_perimeters_vase && !this->m_config.spiral_vase
|
||||
//but not for the first layer
|
||||
&& this->m_layer->id() > 0) {
|
||||
return extrude_loop_vase(original_loop, description, speed, lower_layer_edge_grid);
|
||||
}
|
||||
|
||||
// get a copy; don't modify the orientation of the original loop object otherwise
|
||||
// next copies (if any) would not detect the correct orientation
|
||||
ExtrusionLoop loop = original_loop;
|
||||
|
||||
if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) {
|
||||
if (! *lower_layer_edge_grid) {
|
||||
// Create the distance field for a layer below.
|
||||
const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
|
||||
*lower_layer_edge_grid = make_unique<EdgeGrid::Grid>();
|
||||
(*lower_layer_edge_grid)->create(m_layer->lower_layer->slices, distance_field_resolution);
|
||||
(*lower_layer_edge_grid)->calculate_sdf();
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox = (*lower_layer_edge_grid)->bbox();
|
||||
bbox.min(0) -= scale_(5.f);
|
||||
bbox.min(1) -= scale_(5.f);
|
||||
bbox.max(0) += scale_(5.f);
|
||||
bbox.max(1) += scale_(5.f);
|
||||
EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// extrude all loops ccw
|
||||
//no! this was decided in perimeter_generator
|
||||
bool was_clockwise = false;// loop.make_counter_clockwise();
|
||||
//if spiral vase, we have to ensure that all loops are in the same orientation.
|
||||
if (this->m_config.spiral_vase) {
|
||||
was_clockwise = loop.make_counter_clockwise();
|
||||
}
|
||||
|
||||
split_at_seam_pos(loop, lower_layer_edge_grid);
|
||||
|
||||
// clip the path to avoid the extruder to get exactly on the first point of the loop;
|
||||
// if polyline was shorter than the clipping distance we'd get a null polyline, so
|
||||
|
@ -235,10 +235,12 @@ protected:
|
||||
virtual void use(const ExtrusionEntityCollection &collection) override;
|
||||
std::string extrude_entity(const ExtrusionEntity &entity, const std::string &description, double speed = -1., std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid = nullptr);
|
||||
std::string extrude_loop(const ExtrusionLoop &loop, const std::string &description, double speed = -1., std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid = nullptr);
|
||||
std::string extrude_loop_vase(const ExtrusionLoop &loop, const std::string &description, double speed = -1., std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid = nullptr);
|
||||
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, const std::string &description, double speed = -1.);
|
||||
std::string extrude_multi_path3D(const ExtrusionMultiPath3D &multipath, const std::string &description, double speed = -1.);
|
||||
std::string extrude_path(const ExtrusionPath &path, const std::string &description, double speed = -1.);
|
||||
std::string extrude_path_3D(const ExtrusionPath3D &path, const std::string &description, double speed = -1.);
|
||||
void split_at_seam_pos(ExtrusionLoop &loop, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid);
|
||||
|
||||
typedef std::vector<int> ExtruderPerCopy;
|
||||
// Extruding multiple objects with soluble / non-soluble / combined supports
|
||||
|
@ -633,13 +633,22 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(50, true));
|
||||
|
||||
def = this->add("external_perimeters_first", coBool);
|
||||
def->label = L("External perimeters first");
|
||||
def->label = L("first");
|
||||
def->full_label = L("External perimeters first");
|
||||
def->category = OptionCategory::perimeter;
|
||||
def->tooltip = L("Print contour perimeters from the outermost one to the innermost one "
|
||||
"instead of the default inverse order.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("external_perimeters_vase", coBool);
|
||||
def->label = L("in vase mode (no seam)");
|
||||
def->full_label = L("ExternalPerimeter in vase mode");
|
||||
def->category = OptionCategory::perimeter;
|
||||
def->tooltip = L("Print contour perimeters in two circle, in a contiunous way, like for a vase mode. It needs the external_perimeters_first parameter do work.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("perimeter_loop", coBool);
|
||||
def->label = L(" ");
|
||||
def->full_label = L("Perimeters loop");
|
||||
|
@ -470,6 +470,7 @@ public:
|
||||
ConfigOptionFloat elefant_foot_compensation;
|
||||
ConfigOptionBool exact_last_layer_height;
|
||||
ConfigOptionFloatOrPercent extrusion_width;
|
||||
ConfigOptionBool external_perimeters_vase;
|
||||
ConfigOptionFloatOrPercent first_layer_height;
|
||||
ConfigOptionBool infill_only_where_needed;
|
||||
// Force the generation of solid shells between adjacent materials/volumes.
|
||||
@ -523,6 +524,7 @@ protected:
|
||||
OPT_PTR(elefant_foot_compensation);
|
||||
OPT_PTR(exact_last_layer_height);
|
||||
OPT_PTR(extrusion_width);
|
||||
OPT_PTR(external_perimeters_vase);
|
||||
OPT_PTR(first_layer_height);
|
||||
OPT_PTR(infill_only_where_needed);
|
||||
OPT_PTR(interface_shells);
|
||||
|
@ -709,6 +709,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|
||||
|| opt_key == "support_material_interface_speed"
|
||||
|| opt_key == "bridge_speed"
|
||||
|| opt_key == "external_perimeter_speed"
|
||||
|| opt_key == "external_perimeters_vase"
|
||||
|| opt_key == "infill_speed"
|
||||
|| opt_key == "perimeter_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
|
@ -242,10 +242,13 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
{
|
||||
bool have_perimeters = config->opt_int("perimeters") > 0;
|
||||
for (auto el : { "extra_perimeters", "extra_perimeters_odd_layers", "only_one_perimeter_top", "ensure_vertical_shell_thickness", "thin_walls", "overhangs",
|
||||
"seam_position", "external_perimeters_first", "external_perimeter_extrusion_width",
|
||||
"seam_position", "external_perimeters_first", "external_perimeters_vase", "external_perimeter_extrusion_width",
|
||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "perimeter_loop", "perimeter_loop_seam" })
|
||||
toggle_field(el, have_perimeters);
|
||||
|
||||
for (auto el : { "external_perimeters_vase"})
|
||||
toggle_field(el, config->opt_bool("external_perimeters_first"));
|
||||
|
||||
for (auto el : { "thin_walls_min_width", "thin_walls_overlap" })
|
||||
toggle_field(el, config->opt_bool("thin_walls"));
|
||||
|
||||
|
@ -383,7 +383,10 @@ const std::vector<std::string>& Preset::print_options()
|
||||
"extra_perimeters_odd_layers",
|
||||
"only_one_perimeter_top", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
|
||||
"overhangs_width",
|
||||
"seam_position", "external_perimeters_first", "fill_density"
|
||||
"seam_position",
|
||||
"external_perimeters_first",
|
||||
"external_perimeters_vase",
|
||||
"fill_density"
|
||||
, "fill_pattern"
|
||||
, "fill_top_flow_ratio"
|
||||
, "fill_smooth_width"
|
||||
|
@ -1078,7 +1078,10 @@ void TabPrint::build()
|
||||
line.append_option(optgroup->get_option("seam_position"));
|
||||
line.append_option(optgroup->get_option("seam_travel"));
|
||||
optgroup->append_line(line);
|
||||
optgroup->append_single_option_line("external_perimeters_first");
|
||||
line = { _(L("External Perimeter")), "" };
|
||||
line.append_option(optgroup->get_option("external_perimeters_first"));
|
||||
line.append_option(optgroup->get_option("external_perimeters_vase"));
|
||||
optgroup->append_line(line);
|
||||
line = { _(L("Looping perimeter")), "" };
|
||||
line.append_option(optgroup->get_option("perimeter_loop"));
|
||||
line.append_option(optgroup->get_option("perimeter_loop_seam"));
|
||||
|
Loading…
x
Reference in New Issue
Block a user