Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full re-write of spiral vase #3091

Merged
merged 6 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the current printobject's region config here

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comments, this check needs to be kept, otherwise we could produce NaN.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it, easy fix. We can't revert this bit to what it was before because of the zero length segments that appear in the gcode but we can definitely avoid doing the computation that explodes and just omit that segment altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixed now! I can't repro so @Noisyfox if you can give this another try or send me repro steps that would be great.

// 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);
andrewboktor marked this conversation as resolved.
Show resolved Hide resolved
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));
andrewboktor marked this conversation as resolved.
Show resolved Hide resolved
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
andrewboktor marked this conversation as resolved.
Show resolved Hide resolved
{
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