Many fixes to "no seam" option for external perimeter first.

Also some option to only apply to hole (or others)
Also some fixes to some loops options in the codepath.
also a test to thin walls, suspected a bug but can't find it.
This commit is contained in:
supermerill 2020-03-22 15:25:21 +01:00
parent 1bf9afbd80
commit 1984df6d49
12 changed files with 229 additions and 43 deletions

View File

@ -41,6 +41,8 @@ group:Advanced
line:External Perimeter
setting:external_perimeters_first
setting:external_perimeters_vase
setting:external_perimeters_nothole
setting:external_perimeters_hole
end_line
line:Looping perimeter
setting:perimeter_loop

View File

@ -39,12 +39,14 @@ enum ExtrusionRole : uint8_t {
// Special flags describing loop
enum ExtrusionLoopRole : uint16_t {
elrDefault=0x1,
elrDefault = 0,
// doesn't contains more contour: it's the most internal one
elrInternal=0x10,
elrSkirt = 0x100,
elrInternal = 1 << 1, //2
elrSkirt = 1 << 2, //4
//it's a modifier that indicate that the loop is around a hole, not around the infill
elrHole = 0x1000,
elrHole = 1 << 3, // 16
//it's a modifier that indicate that the loop should be printed as vase
elrVase = 1 << 4, //32
};
@ -448,7 +450,7 @@ public:
#endif /* NDEBUG */
private:
ExtrusionLoopRole m_loop_role;
ExtrusionLoopRole m_loop_role{ elrDefault };
};
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)

View File

@ -2533,6 +2533,8 @@ std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std:
//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)
{
//don't keep the speed
speed = -1;
// 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;
@ -2561,6 +2563,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
// extrude all loops ccw
//no! this was decided in perimeter_generator
bool is_hole_loop = loop.loop_role() & ExtrusionLoopRole::elrHole != 0;// loop.make_counter_clockwise();
bool reverse_turn = loop.polygon().is_clockwise() ^ is_hole_loop;
split_at_seam_pos(loop, lower_layer_edge_grid);
@ -2576,9 +2579,9 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
loop.clip_end(clip_length, &paths);
if (paths.empty()) return "";
// apply the small perimeter speed
// apply the small/external? 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);
speed = m_config.external_perimeter_speed.get_abs_value(m_config.perimeter_speed);
//get extrusion length
coordf_t length = 0;
@ -2594,25 +2597,77 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
const coordf_t init_z = bot_init_z + min_height;
//const coordf_t last_z = bot_init_z + this->m_layer->height;
Point inward_point;
//move the seam point inward a little bit
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 (reverse_turn) {
// swap points
Point c = a; a = b; b = c;
}
double angle = paths.front().first_point().ccw_angle(a, b)*2 / 3;
// turn left if contour, turn right if hole
if (reverse_turn) 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!
inward_point = ((nd * nd >= l2) ? p2 : (p1 + v * (nd / sqrt(l2)))).cast<coord_t>();
inward_point.rotate(angle, paths.front().polyline.points.front());
}
coordf_t current_pos_in_length = 0;
coordf_t current_z = init_z;
coordf_t current_z = 0; // over init_z
coordf_t current_height = min_height;
coordf_t starting_height = min_height;
enum Step {
INCR = 0,
FLAT = 1
};
std::string gcode;
for (int step = 0; step < 2; step++) {
current_pos_in_length = 0;
current_z = 0;
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;
starting_height = this->m_layer->height - min_height;
}
Vec3d previous;
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
if (path == paths.begin() ){
if (step == Step::INCR) {
if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
paths[0].polyline.points.insert(paths[0].polyline.points.begin(), inward_point);
}
this->m_writer.travel_to_z(this->m_layer->print_z + init_z);
} else {
//ensure we're at the right height
this->m_writer.travel_to_z(this->m_layer->print_z);
}
}
gcode += this->_before_extrude(*path, description, speed);
if (path == paths.begin() && step == Step::INCR){
if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
paths[0].polyline.points.erase(paths[0].polyline.points.begin());
gcode += m_writer.extrude_to_xy(this->point_to_gcode(paths[0].polyline.points.front()), 0);
}
}
// 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;
@ -2636,9 +2691,10 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
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,
e_per_mm_per_height * (line_length / nb_sections) * current_height_internal,
description);
current_height_internal += height_increment;
last_point = new_point;
}
//last bit will go to the exact last pos
last_point.x() = this->point_to_gcode(line.b).x();
@ -2646,14 +2702,14 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
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,
e_per_mm_per_height * (line_length / nb_sections) * 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;
current_z = current_pos_in_length * z_per_length;//last_point.z();
current_height = starting_height + current_pos_in_length * height_per_length;
}
}
gcode += this->_after_extrude(*path);
@ -2696,7 +2752,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
//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 (is_hole_loop) {
if (reverse_turn) {
// swap points
Point c = a; a = b; b = c;
}
@ -2704,7 +2760,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
double angle = paths.front().first_point().ccw_angle(a, b) / 3;
// turn left if contour, turn right if hole
if (is_hole_loop) angle *= -1;
if (reverse_turn) 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
@ -2716,10 +2772,11 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s
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());
inward_point = ((nd * nd >= l2) ? p2 : (p1 + v * (nd / sqrt(l2)))).cast<coord_t>();
inward_point.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");
gcode += m_writer.travel_to_xy(this->point_to_gcode(inward_point), "move inwards before travel");
}
return gcode;
@ -2895,7 +2952,7 @@ void GCode::split_at_seam_pos(ExtrusionLoop &loop, std::unique_ptr<EdgeGrid::Gri
loop.split_at(polygon.points[idx_min], true);
} else if (seam_position == spRandom) {
if (loop.loop_role() & elrInternal != 0) {
if (loop.loop_role() & elrInternal == 0) {
// This loop does not contain any other (not-hole) loop. Set a random position.
// The other loops will get a seam close to the random point chosen
// on the inner most contour.
@ -2928,13 +2985,13 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
#endif
//no-seam code path redirect
if (original_loop.role() == ExtrusionRole::erExternalPerimeter && this->m_config.external_perimeters_vase && !this->m_config.spiral_vase
if (original_loop.role() == ExtrusionRole::erExternalPerimeter && (original_loop.loop_role() & elrVase) != 0 && !this->m_config.spiral_vase
//but not for the first layer
&& this->m_layer->id() > 0
//exclude if min_layer_height * 2 > layer_height (increase from 2 to 3 because it's working but uses in-between)
&& this->m_layer->height >= EXTRUDER_CONFIG(min_layer_height) * 2
&& this->m_layer->height >= EXTRUDER_CONFIG(min_layer_height) * 2 - EPSILON
) {
std::cout << " ok, loop vase @"<< this->m_layer->id()<<", "<< this->m_layer->print_z<<"\n";
return extrude_loop_vase(original_loop, description, speed, lower_layer_edge_grid);
}

View File

@ -131,6 +131,9 @@ void Layer::make_perimeters()
&& config.perimeter_speed == other_config.perimeter_speed // it os mandatory? can't this be set at gcode.cpp?
&& config.external_perimeter_extrusion_width == other_config.external_perimeter_extrusion_width
&& config.external_perimeters_first == other_config.external_perimeters_first
&& config.external_perimeters_vase == other_config.external_perimeters_vase
&& config.external_perimeters_hole == other_config.external_perimeters_hole
&& config.external_perimeters_nothole == other_config.external_perimeters_nothole
&& config.external_perimeter_speed == other_config.external_perimeter_speed
&& config.extra_perimeters_odd_layers == other_config.extra_perimeters_odd_layers
&& config.gap_fill == other_config.gap_fill

View File

@ -588,6 +588,7 @@ void PerimeterGenerator::process()
}
}
// at this point, all loops should be in contours[0] (= contours.front() )
// collection of loops to add into loops
ExtrusionEntityCollection entities;
if (config->perimeter_loop.value) {
//onlyone_perimter = >fusion all perimeterLoops
@ -615,8 +616,45 @@ void PerimeterGenerator::process()
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
if (this->config->external_perimeters_first ||
(this->layer_id == 0 && this->print_config->brim_width.value > 0))
(this->layer_id == 0 && this->print_config->brim_width.value > 0)) {
if (this->config->external_perimeters_nothole.value) {
if (this->config->external_perimeters_hole.value) {
entities.reverse();
} else {
//reverse only not-hole perimeters
ExtrusionEntityCollection coll2;
for (const auto loop : entities.entities) {
std::cout << loop->is_loop() <<" test " << (((ExtrusionLoop*)loop)->loop_role()) <<" & " << ExtrusionLoopRole::elrHole <<"\n";
if (loop->is_loop() && !(((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
coll2.entities.push_back(loop);
}
}
coll2.reverse();
for (const auto loop : entities.entities) {
if (!loop->is_loop() || (((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
coll2.entities.push_back(loop);
}
}
entities = coll2;
}
} else if (this->config->external_perimeters_hole.value) {
//reverse the hole, and put them in first place.
ExtrusionEntityCollection coll2;
for (const auto loop : entities.entities) {
if (loop->is_loop() && (((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
coll2.entities.push_back(loop);
}
}
coll2.reverse();
for (const auto loop : entities.entities) {
if (!loop->is_loop() || !(((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
coll2.entities.push_back(loop);
}
}
entities = coll2;
}
}
// append perimeters for this slice as a collection
if (!entities.empty())
this->loops->append(entities);
@ -708,18 +746,21 @@ ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
bool is_external = loop.is_external();
ExtrusionRole role;
ExtrusionLoopRole loop_role;
ExtrusionLoopRole loop_role = ExtrusionLoopRole::elrDefault;
role = is_external ? erExternalPerimeter : erPerimeter;
if (loop.is_internal_contour()) {
// Note that we set loop role to ContourInternalPerimeter
// also when loop is both internal and external (i.e.
// there's only one contour loop).
loop_role = elrInternal;
} else {
loop_role = elrDefault;
loop_role = ExtrusionLoopRole::elrInternal;
}
if (!loop.is_contour) {
loop_role = (ExtrusionLoopRole)(loop_role | elrHole);
loop_role = (ExtrusionLoopRole)(loop_role | ExtrusionLoopRole::elrHole);
}
if (this->config->external_perimeters_vase.value && this->config->external_perimeters_first.value && is_external) {
if ((loop.is_contour && this->config->external_perimeters_nothole.value) || (!loop.is_contour && this->config->external_perimeters_hole.value)) {
loop_role = (ExtrusionLoopRole)(loop_role | ExtrusionLoopRole::elrVase);
}
}
// detect overhanging/bridging perimeters

View File

@ -668,6 +668,25 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("external_perimeters_nothole", coBool);
def->label = L("only for outter side");
def->full_label = L("ext peri first for outter side");
def->category = OptionCategory::perimeter;
def->tooltip = L("Only do the vase trick on the external side. Useful when the thikness is too low.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("external_perimeters_hole", coBool);
def->label = L("only for inner side");
def->full_label = L("ext peri first for inner side");
def->category = OptionCategory::perimeter;
def->tooltip = L("Only do the vase trick on the external side. Useful when you only want to remode seam from screw hole.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
ConfigOptionBool external_perimeters_nothole;
ConfigOptionBool external_perimeters_hole;
def = this->add("perimeter_loop", coBool);
def->label = L("");
def->full_label = L("Perimeters loop");
@ -679,7 +698,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("perimeter_loop_seam", coEnum);
def->label = L("Seam position");
def->full_label = L("Perimeter loop");
def->full_label = L("Perimeter loop seam");
def->category = OptionCategory::perimeter;
def->tooltip = L("Position of perimeters starting points.");
def->enum_keys_map = &ConfigOptionEnum<SeamPosition>::get_enum_values();

View File

@ -492,7 +492,6 @@ 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.
@ -546,7 +545,6 @@ 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);
@ -611,6 +609,9 @@ public:
ConfigOptionFloatOrPercent external_perimeter_extrusion_width;
ConfigOptionFloatOrPercent external_perimeter_speed;
ConfigOptionBool external_perimeters_first;
ConfigOptionBool external_perimeters_vase;
ConfigOptionBool external_perimeters_nothole;
ConfigOptionBool external_perimeters_hole;
ConfigOptionBool extra_perimeters;
ConfigOptionBool extra_perimeters_odd_layers;
ConfigOptionBool only_one_perimeter_top;
@ -683,6 +684,9 @@ protected:
OPT_PTR(external_perimeter_extrusion_width);
OPT_PTR(external_perimeter_speed);
OPT_PTR(external_perimeters_first);
OPT_PTR(external_perimeters_vase);
OPT_PTR(external_perimeters_nothole);
OPT_PTR(external_perimeters_hole);
OPT_PTR(extra_perimeters);
OPT_PTR(extra_perimeters_odd_layers);
OPT_PTR(only_one_perimeter_top);

View File

@ -611,6 +611,9 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "thin_walls_min_width"
|| opt_key == "thin_walls_overlap"
|| opt_key == "external_perimeters_first"
|| opt_key == "external_perimeters_vase"
|| opt_key == "external_perimeters_nothole"
|| opt_key == "external_perimeters_hole"
|| opt_key == "perimeter_loop"
|| opt_key == "perimeter_loop_seam"
|| opt_key == "only_one_perimeter_top"

View File

@ -246,7 +246,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "perimeter_loop", "perimeter_loop_seam" })
toggle_field(el, have_perimeters);
for (auto el : { "external_perimeters_vase"})
for (auto el : { "external_perimeters_vase", "external_perimeters_nothole", "external_perimeters_hole"})
toggle_field(el, config->opt_bool("external_perimeters_first"));
for (auto el : { "thin_walls_min_width", "thin_walls_overlap" })
@ -254,6 +254,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("perimeter_loop_seam", config->opt_bool("perimeter_loop"));
toggle_field("gap_fill_min_area", config->opt_bool("gap_fill"));
bool have_infill = config->option<ConfigOptionPercent>("fill_density")->value > 0;
// infill_extruder uses the same logic as in Print::extruders()

View File

@ -405,6 +405,8 @@ const std::vector<std::string>& Preset::print_options()
"seam_position",
"external_perimeters_first",
"external_perimeters_vase",
"external_perimeters_nothole",
"external_perimeters_hole",
"fill_density"
, "fill_pattern"
, "fill_top_flow_ratio"

View File

@ -1557,6 +1557,8 @@ void TabPrint::build()
line = { _(L("External Perimeter")), "" };
line.append_option(optgroup->get_option("external_perimeters_first"));
line.append_option(optgroup->get_option("external_perimeters_vase"));
line.append_option(optgroup->get_option("external_perimeters_nothole"));
line.append_option(optgroup->get_option("external_perimeters_hole"));
optgroup->append_line(line);
line = { _(L("Looping perimeter")), "" };
line.append_option(optgroup->get_option("perimeter_loop"));

View File

@ -6,10 +6,60 @@
#include "../../libslic3r/ClipperUtils.hpp"
#include "../../libslic3r/MedialAxis.hpp"
#include "../../libslic3r/SVG.hpp"
#include "../../libslic3r/GCode.hpp"
using namespace Slic3r;
using namespace Slic3r::Geometry;
using namespace Slic3r::Test;
class ExtrusionVolumeVisitor : public ExtrusionVisitorConst {
double volume = 0;
public:
virtual void use(const ExtrusionPath &path) override {
for (int i = 0; i < path.polyline.size() - 1; i++) volume += path.polyline.points[i].distance_to(path.polyline.points[i + 1]) * path.mm3_per_mm;
};
virtual void use(const ExtrusionPath3D &path3D) override { std::cout << "error, not supported"; };
virtual void use(const ExtrusionMultiPath &multipath) override {
for (const ExtrusionPath &path : multipath.paths) use(path);
}
virtual void use(const ExtrusionMultiPath3D &multipath) override { std::cout << "error, not supported"; };
virtual void use(const ExtrusionLoop &loop) override {
for (const ExtrusionEntity &path : loop.paths) path.visit(*this);
}
virtual void use(const ExtrusionEntityCollection &collection) override {
for (const ExtrusionEntity *path : collection.entities) path->visit(*this);
}
double compute(const ExtrusionEntity &entity) && {
entity.visit(*this);
return volume;
}
};
SCENARIO("extrude_thinwalls") {
GIVEN("ThickLine") {
ExPolygon expolygon;
expolygon.contour = Slic3r::Polygon{ Points{
Point::new_scale(-0.5, 0),
Point::new_scale(0.5, 0),
Point::new_scale(0.3, 10),
Point::new_scale(-0.3, 10) } };
ThickPolylines res;
MedialAxis{ expolygon, scale_(1.1), scale_(0.5), scale_(0.2) }.build(res);
Flow periflow{ 1.1, 0.2, 0.4 };
ExtrusionEntityCollection gap_fill = thin_variable_width(res, erGapFill, periflow);
//std::string gcode = gcodegen.get_visitor_gcode();
THEN("analyse extrusion.") {
ExtrusionVolumeVisitor vis;
std::cout << " volume is " << ExtrusionVolumeVisitor{}.compute(gap_fill) << "\n";
std::cout << " wanted volume is " << ((0.6*0.2 * 10) + (0.2*0.2 * 10)) << "\n";
REQUIRE(std::abs(ExtrusionVolumeVisitor{}.compute(gap_fill) - ((0.6*0.2 * 10) + (0.2*0.2 * 10)))<0.01);
}
}
}
SCENARIO("thin walls: ")
{