ENH: Full re-write of spiral vase

Github: 2744

* Practically full re-write of spiral vase
- Adds transition out to prevent sharp edge at the top of spiral vase.
- Adds XY interpolation
- Adds option to turn XY interpolation on/off

* - Increasing E to 5 decimal digits (I observed uneven flow with less than that)
- Excluding all travel moves (I saw a bug where somehow we ended up with travel moves within the print so excluding all travel moves)

* - max_xy_smoothing is now configurable, default is 200% of nozzle_diameter
- fixed no-op travel moves in the middle of spiral that now show up as defects when Smooth Spiral is enabled!

* - Avoiding namespace pollution
- Fixing dist_XY == 0 bug

---------

Co-authored-by: Andrew Boktor <aboktor@microsoft.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Change-Id: I4e982b6192f730037ff497389454313af3905e82
This commit is contained in:
andrewboktor 2023-12-16 21:48:21 -08:00 committed by Lane.Wei
parent 88db3a9124
commit 449cb0b27c
8 changed files with 195 additions and 27 deletions

View File

@ -2456,10 +2456,16 @@ void GCode::process_layers(
return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1));
} }
}); });
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order, if (m_spiral_vase) {
[&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult { float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter);
float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter);
this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing);
}
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(
slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult {
spiral_mode.enable(in.spiral_vase_enable); spiral_mode.enable(in.spiral_vase_enable);
return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; bool last_layer = in.layer_id == layers_to_print.size() - 1;
return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
}); });
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order, const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string {
@ -2504,10 +2510,16 @@ void GCode::process_layers(
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder); return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder);
} }
}); });
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order, if (m_spiral_vase) {
[&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in)->GCode::LayerResult { float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter);
float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter);
this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing);
}
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(
slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult {
spiral_mode.enable(in.spiral_vase_enable); spiral_mode.enable(in.spiral_vase_enable);
return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; bool last_layer = in.layer_id == layers_to_print.size() - 1;
return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
}); });
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order, const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string { [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {

View File

@ -1,10 +1,77 @@
#include "SpiralVase.hpp" #include "SpiralVase.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include <sstream> #include <sstream>
#include <cmath>
#include <limits>
namespace Slic3r { namespace Slic3r {
std::string SpiralVase::process_layer(const std::string &gcode) namespace SpiralVaseHelpers {
/** == Smooth Spiral Helpers == */
/** Distance between a and b */
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}
SpiralVase::SpiralPoint subtract(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
{
return SpiralVase::SpiralPoint(a.x - b.x, a.y - b.y);
}
SpiralVase::SpiralPoint add(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
return SpiralVase::SpiralPoint(a.x + b.x, a.y + b.y);
}
SpiralVase::SpiralPoint scale(SpiralVase::SpiralPoint a, float factor) {
return SpiralVase::SpiralPoint(a.x * factor, a.y * factor);
}
/** dot product */
float dot(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
return a.x * b.x + a.y * b.y;
}
/** Find the point on line ab closes to point c */
SpiralVase::SpiralPoint nearest_point_on_line(SpiralVase::SpiralPoint c, SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b, float& dist)
{
SpiralVase::SpiralPoint ab = subtract(b, a);
SpiralVase::SpiralPoint ac = subtract(c, a);
float t = dot(ac, ab) / dot(ab, ab);
t = t > 1 ? 1 : t;
t = t < 0 ? 0 : t;
SpiralVase::SpiralPoint closest = SpiralVase::SpiralPoint(add(a, scale(ab, t)));
dist = distance(c, closest);
return closest;
}
/** Given a set of lines defined by points such as line[n] is the line from points[n] to points[n+1],
* find the closest point to p that falls on any of the lines */
SpiralVase::SpiralPoint nearest_point_on_lines(SpiralVase::SpiralPoint p,
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> points,
bool& found,
float& dist)
{
if (points->size() < 2) {
found = false;
return SpiralVase::SpiralPoint(0, 0);
}
float min = std::numeric_limits<float>::max();
SpiralVase::SpiralPoint closest(0, 0);
for (unsigned long i = 0; i < points->size() - 1; i++) {
float currentDist = 0;
SpiralVase::SpiralPoint current = nearest_point_on_line(p, points->at(i), points->at(i + 1), currentDist);
if (currentDist < min) {
min = currentDist;
closest = current;
found = true;
}
}
dist = min;
return closest;
}
} // namespace SpiralVase
std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
{ {
/* This post-processor relies on several assumptions: /* This post-processor relies on several assumptions:
- all layers are processed through it, including those that are not supposed - all layers are processed through it, including those that are not supposed
@ -49,15 +116,25 @@ std::string SpiralVase::process_layer(const std::string &gcode)
// Remove layer height from initial Z. // Remove layer height from initial Z.
z -= layer_height; z -= layer_height;
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> current_layer = std::make_shared<std::vector<SpiralVase::SpiralPoint>>();
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> previous_layer = m_previous_layer;
bool smooth_spiral = m_smooth_spiral;
std::string new_gcode; std::string new_gcode;
std::string transition_gcode;
float max_xy_dist_for_smoothing = m_max_xy_smoothing;
//FIXME Tapering of the transition layer only works reliably with relative extruder distances. //FIXME Tapering of the transition layer only works reliably with relative extruder distances.
// For absolute extruder distances it will be switched off. // For absolute extruder distances it will be switched off.
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition // Tapering the absolute extruder distances requires to process every extrusion value after the first transition
// layer. // layer.
bool transition = m_transition_layer && m_config.use_relative_e_distances.value; bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
float layer_height_factor = layer_height / total_layer_length; bool transition_out = last_layer && m_config.use_relative_e_distances.value;
float len = 0.f; float len = 0.f;
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] //set initial point
SpiralVase::SpiralPoint last_point = previous_layer != NULL && previous_layer->size() > 0 ? previous_layer->at(previous_layer->size()-1): SpiralVase::SpiralPoint(0,0);
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_layer, &previous_layer, &transition_gcode, transition_out,
smooth_spiral, &max_xy_dist_for_smoothing, &last_point]
(GCodeReader &reader, GCodeReader::GCodeLine line) { (GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) { if (line.cmd_is("G1")) {
if (line.has_z()) { if (line.has_z()) {
@ -69,29 +146,67 @@ std::string SpiralVase::process_layer(const std::string &gcode)
} else { } else {
float dist_XY = line.dist_XY(reader); float dist_XY = line.dist_XY(reader);
if (dist_XY > 0) { if (dist_XY > 0) {
// horizontal move if (line.extruding(reader)) { // Exclude wipe and retract
if (line.extruding(reader)) {
len += dist_XY; len += dist_XY;
line.set(reader, Z, z + len * layer_height_factor); float factor = len / total_layer_length;
if (transition && line.has(E)) if (transition_in)
// Transition layer, modulate the amount of extrusion from zero to the final value. // Transition layer, interpolate the amount of extrusion from zero to the final value.
line.set(reader, E, line.value(E) * len / total_layer_length); line.set(reader, E, line.e() * factor, 5 /*decimal_digits*/);
else if (transition_out) {
// We want the last layer to ramp down extrusion, but without changing z height!
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
// We add this new layer at the very end
GCodeReader::GCodeLine transitionLine(line);
transitionLine.set(reader, E, line.e() * (1 - factor), 5 /*decimal_digits*/);
transition_gcode += transitionLine.raw() + '\n';
}
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
line.set(reader, Z, z + factor * layer_height);
if (smooth_spiral) {
// Now we also need to try to interpolate X and Y
SpiralVase::SpiralPoint p(line.x(), line.y()); // Get current x/y coordinates
current_layer->push_back(p); // Store that point for later use on the next layer
if (previous_layer != NULL) {
bool found = false;
float dist = 0;
SpiralVase::SpiralPoint nearestp = SpiralVaseHelpers::nearest_point_on_lines(p, previous_layer, found, dist);
if (found && dist < max_xy_dist_for_smoothing) {
// Interpolate between the point on this layer and the point on the previous layer
SpiralVase::SpiralPoint target = SpiralVaseHelpers::add(SpiralVaseHelpers::scale(nearestp, 1 - factor), SpiralVaseHelpers::scale(p, factor));
line.set(reader, X, target.x);
line.set(reader, Y, target.y);
// We need to figure out the distance of this new line!
float modified_dist_XY = SpiralVaseHelpers::distance(last_point, target);
// Scale the extrusion amount according to change in length
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5 /*decimal_digits*/);
last_point = target;
} else {
last_point = p;
}
}
}
new_gcode += line.raw() + '\n'; new_gcode += line.raw() + '\n';
} }
return; return;
/* Skip travel moves: the move to first perimeter point will /* Skip travel moves: the move to first perimeter point will
cause a visible seam when loops are not aligned in XY; by skipping cause a visible seam when loops are not aligned in XY; by skipping
it we blend the first loop move in the XY plane (although the smoothness it we blend the first loop move in the XY plane (although the smoothness
of such blend depend on how long the first segment is; maybe we should of such blend depend on how long the first segment is; maybe we should
enforce some minimum length?). */ enforce some minimum length?).
When smooth_spiral is enabled, we're gonna end up exactly where the next layer should
start anyway, so we don't need the travel move */
} }
} }
} }
new_gcode += line.raw() + '\n'; new_gcode += line.raw() + '\n';
if(transition_out) {
transition_gcode += line.raw() + '\n';
}
}); });
return new_gcode; m_previous_layer = current_layer;
return new_gcode + transition_gcode;
} }
} }

View File

@ -6,14 +6,25 @@
namespace Slic3r { namespace Slic3r {
class SpiralVase { class SpiralVase
{
public: public:
class SpiralPoint
{
public:
SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {}
public:
float x, y;
};
SpiralVase(const PrintConfig &config) : m_config(config) SpiralVase(const PrintConfig &config) : m_config(config)
{ {
//BBS //BBS
//m_reader.z() = (float)m_config.z_offset; //m_reader.z() = (float)m_config.z_offset;
m_reader.z() = 0.0f; m_reader.z() = 0.0f;
m_reader.apply_config(m_config); m_reader.apply_config(m_config);
m_previous_layer = NULL;
m_smooth_spiral = config.spiral_mode_smooth;
}; };
void enable(bool en) { void enable(bool en) {
@ -21,17 +32,22 @@ public:
m_enabled = en; m_enabled = en;
} }
std::string process_layer(const std::string &gcode); std::string process_layer(const std::string &gcode, bool last_layer);
void set_max_xy_smoothing(float max) {
m_max_xy_smoothing = max;
}
private: private:
const PrintConfig &m_config; const PrintConfig &m_config;
GCodeReader m_reader; GCodeReader m_reader;
float m_max_xy_smoothing = 0.f;
bool m_enabled = false; bool m_enabled = false;
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height. // First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
bool m_transition_layer = false; bool m_transition_layer = false;
// Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes
bool m_smooth_spiral = false;
std::shared_ptr<std::vector<SpiralPoint>> m_previous_layer;
}; };
} }
#endif // slic3r_SpiralVase_hpp_ #endif // slic3r_SpiralVase_hpp_

View File

@ -792,7 +792,7 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
} }
static std::vector<std::string> s_Preset_print_options { static std::vector<std::string> s_Preset_print_options {
"layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "slicing_mode", "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
"ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall",
"seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max",

View File

@ -3014,6 +3014,25 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple; def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("spiral_mode_smooth", coBool);
def->label = L("Smooth Spiral");
def->tooltip = L("Smooth Spiral smoothes out X and Y moves as well"
"resulting in no visible seam at all, even in the XY directions on walls that are not vertical");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("spiral_mode_max_xy_smoothing", coFloatOrPercent);
def->label = L("Max XY Smoothing");
def->tooltip = L("Maximum distance to move points in XY to try to achieve a smooth spiral"
"If expressed as a %, it will be computed over nozzle diameter");
def->sidetext = L("mm or %");
def->ratio_over = "nozzle_diameter";
def->min = 0;
def->max = 1000;
def->max_literal = 10;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(200, true));
def = this->add("timelapse_type", coEnum); def = this->add("timelapse_type", coEnum);
def->label = L("Timelapse"); def->label = L("Timelapse");
def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. " def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. "

View File

@ -1043,6 +1043,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInt, skirt_loops)) ((ConfigOptionInt, skirt_loops))
((ConfigOptionInts, slow_down_layer_time)) ((ConfigOptionInts, slow_down_layer_time))
((ConfigOptionBool, spiral_mode)) ((ConfigOptionBool, spiral_mode))
((ConfigOptionBool, spiral_mode_smooth))
((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing))
((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionInts, nozzle_temperature)) ((ConfigOptionInts, nozzle_temperature))
((ConfigOptionInts, chamber_temperatures)) ((ConfigOptionInts, chamber_temperatures))

View File

@ -602,6 +602,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("sparse_infill_anchor", has_infill_anchors); toggle_line("sparse_infill_anchor", has_infill_anchors);
bool has_spiral_vase = config->opt_bool("spiral_mode"); bool has_spiral_vase = config->opt_bool("spiral_mode");
toggle_line("spiral_mode_smooth", has_spiral_vase);
toggle_line("spiral_mode_max_xy_smoothing", config->opt_bool("spiral_mode_smooth"));
bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0; bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0;
bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0; bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill; bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill;

View File

@ -2149,6 +2149,8 @@ void TabPrint::build()
optgroup->append_single_option_line("slicing_mode"); optgroup->append_single_option_line("slicing_mode");
optgroup->append_single_option_line("print_sequence", "sequent-print"); optgroup->append_single_option_line("print_sequence", "sequent-print");
optgroup->append_single_option_line("spiral_mode", "spiral-vase"); optgroup->append_single_option_line("spiral_mode", "spiral-vase");
optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth");
optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing");
optgroup->append_single_option_line("timelapse_type", "Timelapse"); optgroup->append_single_option_line("timelapse_type", "Timelapse");
optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin");