add "vase mode / no seam" option for external perimeters.

This commit is contained in:
supermerill 2019-11-07 18:29:34 +01:00
parent 7854bed9c4
commit 9ae142ef3e
8 changed files with 281 additions and 69 deletions

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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);

View File

@ -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"

View File

@ -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"));

View File

@ -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"

View File

@ -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"));