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:
parent
88db3a9124
commit
449cb0b27c
|
@ -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));
|
||||
}
|
||||
});
|
||||
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult {
|
||||
if (m_spiral_vase) {
|
||||
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);
|
||||
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,
|
||||
[&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);
|
||||
}
|
||||
});
|
||||
const auto spiral_mode = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in)->GCode::LayerResult {
|
||||
if (m_spiral_vase) {
|
||||
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);
|
||||
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,
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {
|
||||
|
|
|
@ -1,10 +1,77 @@
|
|||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
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:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
|
@ -45,19 +112,29 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Remove layer height from initial Z.
|
||||
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 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.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
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, ¤t_layer, &previous_layer, &transition_gcode, transition_out,
|
||||
smooth_spiral, &max_xy_dist_for_smoothing, &last_point]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
|
@ -69,29 +146,67 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
if (line.extruding(reader)) { // Exclude wipe and retract
|
||||
len += dist_XY;
|
||||
line.set(reader, Z, z + len * layer_height_factor);
|
||||
if (transition && line.has(E))
|
||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
||||
float factor = len / total_layer_length;
|
||||
if (transition_in)
|
||||
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||
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';
|
||||
}
|
||||
return;
|
||||
|
||||
/* Skip travel moves: the move to first perimeter point will
|
||||
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
|
||||
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';
|
||||
if(transition_out) {
|
||||
transition_gcode += line.raw() + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
return new_gcode;
|
||||
|
||||
m_previous_layer = current_layer;
|
||||
|
||||
return new_gcode + transition_gcode;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,14 +6,25 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
class SpiralVase
|
||||
{
|
||||
public:
|
||||
class SpiralPoint
|
||||
{
|
||||
public:
|
||||
SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {}
|
||||
|
||||
public:
|
||||
float x, y;
|
||||
};
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
//BBS
|
||||
//m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.z() = 0.0f;
|
||||
m_reader.apply_config(m_config);
|
||||
m_previous_layer = NULL;
|
||||
m_smooth_spiral = config.spiral_mode_smooth;
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
|
@ -21,17 +32,22 @@ public:
|
|||
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:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
float m_max_xy_smoothing = 0.f;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
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_
|
||||
|
|
|
@ -792,7 +792,7 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
|
|||
}
|
||||
|
||||
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",
|
||||
"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",
|
||||
|
|
|
@ -3014,6 +3014,25 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comSimple;
|
||||
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->label = L("Timelapse");
|
||||
def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. "
|
||||
|
|
|
@ -1043,6 +1043,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||
((ConfigOptionInt, skirt_loops))
|
||||
((ConfigOptionInts, slow_down_layer_time))
|
||||
((ConfigOptionBool, spiral_mode))
|
||||
((ConfigOptionBool, spiral_mode_smooth))
|
||||
((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing))
|
||||
((ConfigOptionInt, standby_temperature_delta))
|
||||
((ConfigOptionInts, nozzle_temperature))
|
||||
((ConfigOptionInts, chamber_temperatures))
|
||||
|
|
|
@ -602,6 +602,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
|||
toggle_line("sparse_infill_anchor", has_infill_anchors);
|
||||
|
||||
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_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill;
|
||||
|
|
|
@ -2149,6 +2149,8 @@ void TabPrint::build()
|
|||
optgroup->append_single_option_line("slicing_mode");
|
||||
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_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("fuzzy_skin", "parameter/fuzzy-skin");
|
||||
|
|
Loading…
Reference in New Issue