Skip to content

Commit

Permalink
Practically full re-write of spiral vase
Browse files Browse the repository at this point in the history
- Adds transition out to prevent sharp edge at the top of spiral vase.
- Adds XY interpolation
- Adds option to turn XY interpolation on/off
  • Loading branch information
aboktor committed Dec 12, 2023
1 parent 057926f commit fea308e
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 20 deletions.
10 changes: 6 additions & 4 deletions src/libslic3r/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2729,12 +2729,13 @@ void GCode::process_layers(
});

const auto spiral_mode = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_mode = *this->m_spiral_vase.get()](LayerResult in) -> LayerResult {
[&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult {
if (in.nop_layer_result)
return in;

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 pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
Expand Down Expand Up @@ -2810,9 +2811,10 @@ void GCode::process_layers(
}
});
const auto spiral_mode = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_mode = *this->m_spiral_vase.get()](LayerResult in)->LayerResult {
[&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->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<LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer.get()](LayerResult in)->std::string {
Expand Down
137 changes: 123 additions & 14 deletions src/libslic3r/GCode/SpiralVase.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,70 @@
#include "SpiralVase.hpp"
#include "GCode.hpp"
#include <sstream>
#include <cmath>
#include <limits>

namespace Slic3r {

std::string SpiralVase::process_layer(const std::string &gcode)
/** == Smooth Spiral Helpers == */
/** Distance between a and b */
float distance(SpiralPoint a, SpiralPoint b) {
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y, 2));
}

SpiralPoint subtract(SpiralPoint a, SpiralPoint b) {
return SpiralPoint(a.x-b.x, a.y-b.y);
}

SpiralPoint add(SpiralPoint a, SpiralPoint b) {
return SpiralPoint(a.x+b.x, a.y+b.y);
}

SpiralPoint scale(SpiralPoint a, float factor){
return SpiralPoint(a.x*factor, a.y*factor);
}

/** dot product */
float dot(SpiralPoint a, SpiralPoint b) {
return a.x*b.x+a.y*b.y;
}

/** Find the point on line ab closes to point c */
SpiralPoint nearest_point_on_line(SpiralPoint c, SpiralPoint a, SpiralPoint b, float& dist) {
SpiralPoint ab = subtract(b, a);
SpiralPoint ca = subtract(c, a);
float t = dot(ca, ab)/dot(ab,ab);
t=t>1?1:t;
t=t<0?0:t;
SpiralPoint closest= 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 */
SpiralPoint nearest_point_on_polygon(SpiralPoint p, std::vector<SpiralPoint>* points, bool& found, float& dist) {
if(points->size()<2) {
found=false;
return SpiralPoint(0,0);
}
float min = std::numeric_limits<float>::max();
SpiralPoint closest(0,0);
for(unsigned long i=0; i<points->size()-1; i++) {
float currentDist=0;
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;

}

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
Expand Down Expand Up @@ -45,19 +105,27 @@ std::string SpiralVase::process_layer(const std::string &gcode)
}
});
}

// Remove layer height from initial Z.
z -= layer_height;


std::vector<SpiralPoint>* current_layer = new std::vector<SpiralPoint>();
std::vector<SpiralPoint>* previous_layer = m_previous_layer;

bool smooth_spiral = m_smooth_spiral;
std::string new_gcode;
std::string transition_gcode;
// TODO: This should be proportional to line_width. Something like 2*line_width should be pretty good.
float max_xy_dist_for_smoothing = 0.8; // Made up threshold to prevent snapping to points too far away, Cura uses (2*line_width)^2
//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]
SpiralPoint last_point = previous_layer != NULL && previous_layer->size() >0? previous_layer->at(previous_layer->size()-1): 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) {
if (line.cmd_is("G1")) {
if (line.has_z()) {
Expand All @@ -70,28 +138,69 @@ std::string SpiralVase::process_layer(const std::string &gcode)
float dist_XY = line.dist_XY(reader);
if (dist_XY > 0) {
// horizontal move
if (line.extruding(reader)) {
if (line.extruding(reader)) { // We need this to exclude retract and wipe moves!
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);
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));
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
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;
SpiralPoint nearestp = nearest_point_on_polygon(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
SpiralPoint target = add(scale(nearestp, 1 - factor), 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 = distance(last_point, target);
line.set(reader, E,
line.e() * modified_dist_XY / dist_XY); // Scale the extrusion amount according to change in length
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';
}
});

delete m_previous_layer;
m_previous_layer = current_layer;

return new_gcode;
return new_gcode + transition_gcode;
}

}
19 changes: 18 additions & 1 deletion src/libslic3r/GCode/SpiralVase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,32 @@

namespace Slic3r {

class SpiralPoint
{
public:
SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {}
public:
float x, y;

};

class SpiralVase {

public:
SpiralVase(const PrintConfig &config) : m_config(config)
{
m_reader.z() = (float)m_config.z_offset;
m_reader.apply_config(m_config);
m_previous_layer = NULL;
m_smooth_spiral = config.spiral_mode_smooth;
};

void enable(bool en) {
m_transition_layer = en && ! m_enabled;
m_enabled = en;
}

std::string process_layer(const std::string &gcode);
std::string process_layer(const std::string &gcode, bool last_layer);

private:
const PrintConfig &m_config;
Expand All @@ -28,8 +40,13 @@ class SpiralVase {
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::vector<SpiralPoint> * m_previous_layer;
};



}

#endif // slic3r_SpiralVase_hpp_
2 changes: 1 addition & 1 deletion src/libslic3r/Preset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,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", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern",
Expand Down
7 changes: 7 additions & 0 deletions src/libslic3r/PrintConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3427,6 +3427,13 @@ def = this->add("filament_loading_speed", coFloats);
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("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. "
Expand Down
1 change: 1 addition & 0 deletions src/libslic3r/PrintConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, skirt_speed))
((ConfigOptionFloats, slow_down_layer_time))
((ConfigOptionBool, spiral_mode))
((ConfigOptionBool, spiral_mode_smooth))
((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionInts, nozzle_temperature))
((ConfigOptionBools, wipe))
Expand Down
1 change: 1 addition & 0 deletions src/slic3r/GUI/ConfigManipulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_field("infill_anchor", has_infill_anchors);

bool has_spiral_vase = config->opt_bool("spiral_mode");
toggle_line("spiral_mode_smooth", has_spiral_vase);
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;
Expand Down
1 change: 1 addition & 0 deletions src/slic3r/GUI/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2159,6 +2159,7 @@ 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("timelapse_type", "Timelapse");

optgroup->append_single_option_line("fuzzy_skin");
Expand Down

0 comments on commit fea308e

Please sign in to comment.