Implemented wipe tower print path preview.

This commit is contained in:
bubnikv 2017-05-25 22:27:53 +02:00
parent 7d64c465c0
commit e000b22578
19 changed files with 615 additions and 190 deletions

View file

@ -1888,6 +1888,15 @@ sub load_print_object_toolpaths {
Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs);
}
# Create 3D thick extrusion lines for wipe tower extrusions.
sub load_wipe_tower_toolpaths {
my ($self, $print, $colors) = @_;
$self->SetCurrent($self->GetContext) if $self->UseVBOs;
Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
if ($print->step_done(STEP_WIPE_TOWER));
}
sub set_toolpaths_range {
my ($self, $min_z, $max_z) = @_;
$self->volumes->set_range($min_z, $max_z);

View file

@ -206,6 +206,7 @@ sub load_print {
if ($self->IsShown) {
# load skirt and brim
$self->canvas->load_print_toolpaths($self->print, \@colors);
$self->canvas->load_wipe_tower_toolpaths($self->print, \@colors);
foreach my $object (@{$self->print->objects}) {
$self->canvas->load_print_object_toolpaths($object, \@colors);

View file

@ -44,6 +44,7 @@ sub process {
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->make_brim; # must come after make_skirt
$self->make_wipe_tower;
# time to make some statistics
if (0) {
@ -207,19 +208,13 @@ sub make_skirt {
$_->generate_support_material for @{$self->objects};
return if $self->step_done(STEP_SKIRT);
$self->set_step_started(STEP_SKIRT);
# since this method must be idempotent, we clear skirt paths *before*
# checking whether we need to generate them
$self->skirt->clear;
if (!$self->has_skirt) {
$self->set_step_done(STEP_SKIRT);
return;
}
$self->status_cb->(88, "Generating skirt");
$self->_make_skirt();
$self->set_step_started(STEP_SKIRT);
$self->skirt->clear;
if ($self->has_skirt) {
$self->status_cb->(88, "Generating skirt");
$self->_make_skirt();
}
$self->set_step_done(STEP_SKIRT);
}
@ -292,6 +287,27 @@ sub make_brim {
$self->set_step_done(STEP_BRIM);
}
sub make_wipe_tower {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->make_brim;
return if $self->step_done(STEP_WIPE_TOWER);
$self->set_step_started(STEP_WIPE_TOWER);
$self->_clear_wipe_tower;
if ($self->has_wipe_tower) {
# $self->status_cb->(95, "Generating wipe tower");
$self->_make_wipe_tower;
}
$self->set_step_done(STEP_WIPE_TOWER);
}
# Wrapper around the C++ Slic3r::Print::validate()
# to produce a Perl exception without a hang-up on some Strawberry perls.
sub validate

View file

@ -6,7 +6,7 @@ use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM);
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM STEP_WIPE_TOWER);
our %EXPORT_TAGS = (steps => \@EXPORT_OK);
1;

View file

@ -142,115 +142,79 @@ Wipe::wipe(GCode &gcodegen, bool toolchange)
return gcode;
}
WipeTowerIntegration::WipeTowerIntegration(const PrintConfig &print_config) : m_brim_done(false)
{
// Initialize the wipe tower.
auto *wipe_tower = new WipeTowerPrusaMM(
float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value),
float(print_config.wipe_tower_width.value), float(print_config.wipe_tower_per_color_wipe.value));
//wipe_tower->set_retract();
//wipe_tower->set_zhop();
//wipe_tower->set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < 4; ++ i)
wipe_tower->set_extruder(
i,
WipeTowerPrusaMM::parse_material(print_config.filament_type.get_at(i).c_str()),
print_config.temperature.get_at(i),
print_config.first_layer_temperature.get_at(i));
m_impl.reset(wipe_tower);
}
static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt)
{
return Point(scale_(wipe_tower_pt.x - gcodegen.origin().x), scale_(wipe_tower_pt.y - gcodegen.origin().y));
}
std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const
{
std::string gcode;
// Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, tcr.start_pos),
erMixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
// Let the tool change be executed by the wipe tower class.
// Inform the G-code writer about the changes done behind its back.
gcode += tcr.gcode;
// Accumulate the elapsed time for the correct calculation of layer cooling.
//FIXME currently disabled as Slic3r PE needs to be updated to differentiate the moves it could slow down
// from the moves it could not.
// gcodegen.m_elapsed_time += tcr.elapsed_time;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
gcodegen.writer().toolchange(new_extruder_id);
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
// Prepare a future wipe.
gcodegen.m_wipe.path.points.clear();
if (new_extruder_id >= 0) {
// Start the wipe at the current position.
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left,
tcr.end_pos.y)));
}
// Let the planner know we are traveling between objects.
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
return gcode;
}
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
{
bool over_wipe_tower = false;
std::string gcode;
if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id)) {
// Move over the wipe tower.
gcode += this->travel_to(gcodegen, m_impl->tool_change(extruder_id, WipeTower::PURPOSE_MOVE_TO_TOWER).second);
// Let the tool change be executed by the wipe tower class.
//FIXME calculate time at the wipe tower and add it to m_elapsed_time
std::pair<std::string, WipeTower::xy> code_and_pos = m_impl->tool_change(extruder_id, WipeTower::PURPOSE_EXTRUDE);
// Inform the G-code writer about the changes done behind its back.
gcode += code_and_pos.first;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
gcodegen.writer().toolchange(extruder_id);
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(code_and_pos.second.x, code_and_pos.second.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, code_and_pos.second));
this->prepare_wipe(gcodegen, code_and_pos.second);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
over_wipe_tower = true;
assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size());
if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < m_tool_changes.size()) {
assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size());
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id);
}
m_brim_done = true;
}
if (finish_layer && ! m_impl->layer_finished()) {
// Last extruder change on the layer or no extruder change at all.
if (! over_wipe_tower)
gcode += this->travel_to(gcodegen, m_impl->finish_layer(WipeTower::PURPOSE_MOVE_TO_TOWER).second);
// Let the tool change be executed by the wipe tower class.
//FIXME calculate time at the wipe tower and add it to m_elapsed_time
std::pair<std::string, WipeTower::xy> code_and_pos = m_impl->finish_layer(WipeTower::PURPOSE_EXTRUDE);
// Inform the G-code writer about the changes done behind its back.
gcode += code_and_pos.first;
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(code_and_pos.second.x, code_and_pos.second.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, code_and_pos.second));
this->prepare_wipe(gcodegen, code_and_pos.second);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
}
return gcode;
}
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
std::string WipeTowerIntegration::finalize(GCode &gcodegen, const Print &print, bool current_layer_full)
std::string WipeTowerIntegration::finalize(GCode &gcodegen)
{
std::string gcode;
// Unload the current filament over the purge tower.
if (current_layer_full) {
// There is not enough space on the wipe tower to purge the nozzle into. Lift Z to the next layer.
coordf_t new_print_z = gcodegen.writer().get_position().z + print.objects.front()->config.layer_height.value;
gcode += gcodegen.change_layer(new_print_z);
m_impl->set_layer(float(new_print_z), float(print.objects.front()->config.layer_height.value), 0, false, true);
}
gcode += this->tool_change(gcodegen, -1, false);
m_impl.release();
if (std::abs(gcodegen.writer().get_position().z - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.change_layer(m_final_purge.print_z);
gcode += append_tcr(gcodegen, m_final_purge, -1);
return gcode;
}
std::string WipeTowerIntegration::travel_to(GCode &gcodegen, const WipeTower::xy &dest)
{
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
std::string gcode = gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, dest),
erMixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
return gcode;
}
void WipeTowerIntegration::prepare_wipe(GCode &gcodegen, const WipeTower::xy &current_position)
{
gcodegen.m_wipe.path.points.clear();
// Start the wipe at the current position.
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, current_position));
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
float l = m_impl->position().x;
float r = l + m_impl->width();
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
WipeTower::xy((std::abs(l - current_position.x) < std::abs(r - current_position.x)) ? r : l,
current_position.y)));
}
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id)
inline void write(FILE *file, const std::string &what)
@ -498,7 +462,10 @@ bool GCode::do_export(FILE *file, Print &print)
}
} else {
// Find tool ordering for all the objects at once, and the initial extruder ID.
tool_ordering = ToolOrdering(print, initial_extruder_id);
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
tool_ordering = print.m_tool_ordering.empty() ?
ToolOrdering(print, initial_extruder_id) :
print.m_tool_ordering;
initial_extruder_id = tool_ordering.first_extruder();
}
if (initial_extruder_id == (unsigned int)-1) {
@ -642,35 +609,24 @@ bool GCode::do_export(FILE *file, Print &print)
// All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (print.config.single_extruder_multi_material.value && print.config.wipe_tower.value &&
if (print.has_wipe_tower() &&
! tool_ordering.empty() && tool_ordering.front().wipe_tower_partitions > 0)
m_wipe_tower.reset(new WipeTowerIntegration(print.config));
m_wipe_tower.reset(new WipeTowerIntegration(print.config, print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
// Extrude the layers.
for (auto &layer : layers_to_print) {
// layer.second is of type std::vector<LayerToPrint>,
// wher the objects are sorted by their sorted order given by object_indices.
const ToolOrdering::LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
if (layer_tools.has_wipe_tower && m_wipe_tower) {
bool first_layer = &layer == layers_to_print.data();
m_wipe_tower->set_layer(
layer.first,
first_layer ?
print.objects.front()->config.first_layer_height.get_abs_value(print.objects.front()->config.layer_height.value) :
print.objects.front()->config.layer_height.value,
layer_tools.wipe_tower_partitions,
first_layer,
layer_tools.wipe_tower_partitions == 0 || (&layer_tools == &tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0));
}
if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer();
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
}
write(file, this->filter(m_cooling_buffer->flush(), true));
if (m_wipe_tower)
// Purge the extruder, pull out the active filament.
write(file, m_wipe_tower->finalize(*this));
}
// write end commands to file
if (m_wipe_tower)
write(file, m_wipe_tower->finalize(*this, print, tool_ordering.back().wipe_tower_partitions > 0 && m_wipe_tower->layer_finished()));
else
write(file, this->retract()); // TODO: process this retract through PressureRegulator in order to discharge fully
write(file, this->retract());
write(file, m_writer.set_fan(false));
writeln(file, m_placeholder_parser.process(print.config.end_gcode));
write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
@ -1036,7 +992,7 @@ void GCode::process_layer(
std::vector<std::unique_ptr<EdgeGrid::Grid>> lower_layer_edge_grids(layers.size());
for (unsigned int extruder_id : layer_tools.extruders)
{
gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ?
gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ?
m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) :
this->set_extruder(extruder_id);

View file

@ -75,18 +75,36 @@ public:
class WipeTowerIntegration {
public:
WipeTowerIntegration(const PrintConfig &config);
void set_layer(coordf_t print_z, coordf_t layer_height, size_t max_tool_changes, bool is_first_layer, bool is_last_layer)
{ m_impl->set_layer(float(print_z), float(layer_height), max_tool_changes, is_first_layer, is_last_layer); }
bool layer_finished() const { return m_impl->layer_finished(); }
WipeTowerIntegration(
const PrintConfig &print_config,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(float(print_config.wipe_tower_x.value)),
m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0),
m_brim_done(false) {}
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen, const Print &print, bool current_layer_full);
std::string finalize(GCode &gcodegen);
private:
std::string travel_to(GCode &codegen, const WipeTower::xy &dest);
void prepare_wipe(GCode &gcodegen, const WipeTower::xy &current_position);
std::unique_ptr<WipeTower> m_impl;
bool m_brim_done;
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
// Reference to cached values at the Printer class.
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
bool m_brim_done;
};
class GCode {

View file

@ -1,3 +1,4 @@
#include "Print.hpp"
#include "ToolOrdering.hpp"
#include <assert.h>

View file

@ -4,10 +4,12 @@
#define slic3r_ToolOrdering_hpp_
#include "libslic3r.h"
#include "Print.hpp"
namespace Slic3r {
class Print;
class PrintObject;
class ToolOrdering
{
public:
@ -47,6 +49,8 @@ public:
// (print.config.complete_objects is false).
ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1);
void clear() { m_layer_tools.clear(); }
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
unsigned int first_extruder() const;
@ -61,6 +65,7 @@ public:
const LayerTools& front() const { return m_layer_tools.front(); }
const LayerTools& back() const { return m_layer_tools.back(); }
bool empty() const { return m_layer_tools.empty(); }
const std::vector<LayerTools>& layer_tools() const { return m_layer_tools; }
private:
void initialize_layers(std::vector<coordf_t> &zs);

View file

@ -3,6 +3,7 @@
#include <utility>
#include <string>
#include <vector>
namespace Slic3r
{
@ -20,6 +21,8 @@ public:
xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; }
xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; }
xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; }
bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; }
float x;
float y;
};
@ -55,18 +58,50 @@ public:
PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE,
};
// Extrusion path of the wipe tower, for 3D preview of the generated tool paths.
struct Extrusion
{
Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
// End position of this extrusion.
xy pos;
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
// This is left zero if it is a travel move.
float width;
// Current extruder index.
unsigned int tool;
};
struct ToolChangeResult
{
// Print heigh of this tool change.
float print_z;
// G-code section to be directly included into the output G-code.
std::string gcode;
// For path preview.
std::vector<Extrusion> extrusions;
// Initial position, at which the wipe tower starts its action.
// At this position the extruder is loaded and there is no Z-hop applied.
xy start_pos;
// Last point, at which the normal G-code generator of Slic3r shall continue.
// At this position the extruder is loaded and there is no Z-hop applied.
xy end_pos;
// Time elapsed over this tool change.
// This is useful not only for the print time estimation, but also for the control of layer cooling.
float elapsed_time;
};
// Returns gcode for toolchange and the end position.
// if new_tool == -1, just unload the current filament over the wipe tower.
virtual std::pair<std::string, xy> tool_change(int new_tool, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0;
virtual ToolChangeResult tool_change(int new_tool, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0;
// Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag.
// Call this method only if layer_finished() is false.
virtual std::pair<std::string, xy> finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0;
virtual ToolChangeResult finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0;
// Is the current layer finished? A layer is finished if either the wipe tower is finished, or
// the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const = 0;
virtual bool layer_finished() const = 0;
};
}; // namespace Slic3r

View file

@ -4,6 +4,7 @@
#include <math.h>
#include <fstream>
#include <iostream>
#include <vector>
#ifdef __linux
#include <strings.h>
@ -25,16 +26,34 @@ public:
m_current_pos(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()),
m_current_z(0.f),
m_current_feedrate(0.f),
m_extrusion_flow(0.f) {}
m_extrusion_flow(0.f),
m_layer_height(0.f),
m_preview_suppressed(false),
m_elapsed_time(0.f) {}
Writer& set_initial_position(const WipeTower::xy &pos) { m_current_pos = pos; return *this; }
Writer& set_initial_position(const WipeTower::xy &pos) {
m_start_pos = pos;
m_current_pos = pos;
return *this;
}
Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; }
Writer& set_z(float z)
{ m_current_z = z; return *this; }
Writer& set_layer_height(float layer_height)
{ m_layer_height = layer_height; return *this; }
Writer& set_extrusion_flow(float flow)
{ m_extrusion_flow = flow; return *this; }
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
// is asked to suppres output of some lines, which look like extrusions.
Writer& suppress_preview() { m_preview_suppressed = true; return *this; }
Writer& resume_preview() { m_preview_suppressed = false; return *this; }
Writer& feedrate(float f)
{
if (f != m_current_feedrate)
@ -43,24 +62,48 @@ public:
}
const std::string& gcode() const { return m_gcode; }
const std::vector<WipeTower::Extrusion>& extrusions() const { return m_extrusions; }
float x() const { return m_current_pos.x; }
float y() const { return m_current_pos.y; }
const WipeTower::xy& start_pos() const { return m_start_pos; }
const WipeTower::xy& pos() const { return m_current_pos; }
float elapsed_time() const { return m_elapsed_time; }
// Extrude with an explicitely provided amount of extrusion.
Writer& extrude_explicit(float x, float y, float e, float f = 0.f)
{
if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
// Neither extrusion nor a travel move.
return *this;
float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y;
double len = sqrt(dx*dx+dy*dy);
if (! m_preview_suppressed && e > 0.f && len > 0.) {
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
// This is left zero if it is a travel move.
float width = float(double(e) * m_filament_area / (len * m_layer_height));
// Correct for the roundings of a squished extrusion.
width += float(m_layer_height * (1. - M_PI / 4.));
if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos)
m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool));
m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool));
}
m_gcode += "G1";
if (x != m_current_pos.x)
m_gcode += set_format_X(x);
if (y != m_current_pos.y)
m_gcode += set_format_Y(y);
if (e != 0.f)
m_gcode += set_format_E(e);
if (f != 0.f && f != m_current_feedrate)
m_gcode += set_format_F(f);
// Update the elapsed time with a rough estimate.
m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f;
m_gcode += "\n";
return *this;
}
@ -146,6 +189,7 @@ public:
char buf[64];
sprintf(buf, "T%d\n", tool);
m_gcode += buf;
m_current_tool = tool;
return *this;
}
@ -222,11 +266,18 @@ public:
Writer& append(const char *text) { m_gcode += text; return *this; }
private:
WipeTower::xy m_start_pos;
WipeTower::xy m_current_pos;
float m_current_z;
float m_current_feedrate;
unsigned int m_current_tool;
float m_layer_height;
float m_extrusion_flow;
bool m_preview_suppressed;
std::string m_gcode;
std::vector<WipeTower::Extrusion> m_extrusions;
float m_elapsed_time;
const double m_filament_area = 0.25*M_PI*1.75*1.75;
std::string set_format_X(float x) {
char buf[64];
@ -260,6 +311,8 @@ private:
m_current_feedrate = f;
return buf;
}
Writer& operator=(const Writer &rhs);
};
} // namespace PrusaMultiMaterial
@ -287,7 +340,7 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam
return INVALID;
}
std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::tool_change(int tool, Purpose purpose)
WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(int tool, Purpose purpose)
{
// Either it is the last tool unload,
// or there must be a nonzero wipe tower partitions available.
@ -306,10 +359,12 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::tool_change(int tool, Pu
PrusaMultiMaterial::Writer writer;
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_layer_height(m_layer_height)
.set_initial_tool(m_current_tool)
.append(";--------------------\n"
"; CP TOOLCHANGE START\n")
.comment_with_value(" toolchange #", m_num_tool_changes)
.comment_material(m_current_material)
.comment_material(m_material[m_current_tool])
.append(";--------------------\n")
.speed_override(100);
@ -318,7 +373,7 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::tool_change(int tool, Pu
if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) {
// Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower.
float initial_retract = ((m_current_material == SCAFF) ? 1.f : 0.5f) * m_retract;
float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract;
writer // Lift for a Z hop.
.z_hop(m_zhop, 7200)
// Additional retract on move to tower.
@ -339,7 +394,7 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::tool_change(int tool, Pu
// Increase the extruder driver current to allow fast ramming.
writer.set_extruder_trimpot(750);
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
toolchange_Unload(writer, cleaning_box, m_current_material,
toolchange_Unload(writer, cleaning_box, m_material[m_current_tool],
m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]);
if (tool >= 0) {
@ -379,10 +434,17 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::tool_change(int tool, Pu
m_current_wipe_start_y += m_wipe_area;
}
return std::pair<std::string, xy>(writer.gcode(), writer.pos());
ToolChangeResult result;
result.print_z = this->m_z_pos;
result.gcode = writer.gcode();
result.elapsed_time = writer.elapsed_time();
result.extrusions = writer.extrusions();
result.start_pos = writer.start_pos();
result.end_pos = writer.pos();
return result;
}
std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset)
WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset)
{
const box_coordinates wipeTower_box(
m_wipe_tower_pos,
@ -393,6 +455,8 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::toolchange_Brim(Purpose
writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
// Let the writer know the current Z position as a base for Z-hop.
.set_z(m_z_pos)
.set_layer_height(m_layer_height)
.set_initial_tool(m_current_tool)
.append(
";-------------------------------------\n"
"; CP WIPE TOWER FIRST LAYER BRIM START\n");
@ -453,7 +517,14 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::toolchange_Brim(Purpose
m_idx_tool_change_in_layer = 0;
}
return std::pair<std::string, xy>(writer.gcode(), writer.pos());
ToolChangeResult result;
result.print_z = this->m_z_pos;
result.gcode = writer.gcode();
result.elapsed_time = writer.elapsed_time();
result.extrusions = writer.extrusions();
result.start_pos = writer.start_pos();
result.end_pos = writer.pos();
return result;
}
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
@ -510,7 +581,8 @@ void WipeTowerPrusaMM::toolchange_Unload(
if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr))
std::swap(xl, xr);
// Horizontal cooling moves will be performed at the following Y coordinate:
writer.travel(xr, writer.y() + y_step * 0.8f, 7200);
writer.travel(xr, writer.y() + y_step * 0.8f, 7200)
.suppress_preview();
switch (current_material)
{
case ABS:
@ -541,7 +613,8 @@ void WipeTowerPrusaMM::toolchange_Unload(
.cool(xl, xr, 5, -3, 2400);
}
writer.flush_planner_queue();
writer.resume_preview()
.flush_planner_queue();
}
// Change the tool, set a speed override for solube and flex materials.
@ -561,7 +634,7 @@ void WipeTowerPrusaMM::toolchange_Change(
writer.set_tool(new_tool)
.speed_override(speed_override)
.flush_planner_queue();
m_current_material = new_material;
m_current_tool = new_tool;
}
void WipeTowerPrusaMM::toolchange_Load(
@ -574,10 +647,12 @@ void WipeTowerPrusaMM::toolchange_Load(
writer.append("; CP TOOLCHANGE LOAD\n")
// Load the filament while moving left / right,
// so the excess material will not create a blob at a single position.
.suppress_preview()
.load_move_x(xr, 20, 1400)
.load_move_x(xl, 40, 3000)
.load_move_x(xr, 20, 1600)
.load_move_x(xl, 10, 1000);
.load_move_x(xl, 10, 1000)
.resume_preview();
// Extrude first five lines (just three lines if colorInit is set).
writer.extrude(xr, writer.y(), 1600);
@ -634,7 +709,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
writer.set_extrusion_flow(m_extrusion_flow);
}
std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::finish_layer(Purpose purpose)
WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose)
{
// This should only be called if the layer is not finished yet.
// Otherwise the caller would likely travel to the wipe tower in vain.
@ -643,6 +718,8 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::finish_layer(Purpose pur
PrusaMultiMaterial::Writer writer;
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_layer_height(m_layer_height)
.set_initial_tool(m_current_tool)
.append(";--------------------\n"
"; CP EMPTY GRID START\n")
// m_num_layer_changes is incremented by set_z, so it is 1 based.
@ -676,7 +753,7 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::finish_layer(Purpose pur
} else {
// The print head is inside the wipe tower. Rather move to the start of the following extrusion.
// writer.set_initial_position(fill_box.ld);
writer.travel(fill_box.ld, 7000);
writer.set_initial_position(fill_box.ld);
}
if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) {
@ -734,7 +811,14 @@ std::pair<std::string, WipeTower::xy> WipeTowerPrusaMM::finish_layer(Purpose pur
m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes;
}
return std::pair<std::string, xy>(writer.gcode(), writer.pos());
ToolChangeResult result;
result.print_z = this->m_z_pos;
result.gcode = writer.gcode();
result.elapsed_time = writer.elapsed_time();
result.extrusions = writer.extrusions();
result.start_pos = writer.start_pos();
result.end_pos = writer.pos();
return result;
}
}; // namespace Slic3r

View file

@ -39,11 +39,13 @@ public:
// y -- y coordinates of wipe tower in mm ( left bottom corner )
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
// wipe_area -- space available for one toolchange in mm
WipeTowerPrusaMM(float x, float y, float width, float wipe_area) :
WipeTowerPrusaMM(float x, float y, float width, float wipe_area, unsigned int initial_tool) :
m_wipe_tower_pos(x, y),
m_wipe_tower_width(width),
m_wipe_area(wipe_area),
m_z_pos(0.f) {
m_z_pos(0.f),
m_current_tool(initial_tool)
{
for (size_t i = 0; i < 4; ++ i) {
// Extruder specific parameters.
m_material[i] = PLA;
@ -51,6 +53,7 @@ public:
m_first_layer_temperature[i] = 0;
}
}
virtual ~WipeTowerPrusaMM() {}
// _retract - retract value in mm
@ -81,6 +84,7 @@ public:
bool is_last_layer)
{
m_z_pos = print_z;
m_layer_height = layer_height;
m_max_color_changes = max_tool_changes;
m_is_first_layer = is_first_layer;
m_is_last_layer = is_last_layer;
@ -105,24 +109,24 @@ public:
}
// Return the wipe tower position.
virtual const xy& position() const { return m_wipe_tower_pos; }
virtual const xy& position() const { return m_wipe_tower_pos; }
// Return the wipe tower width.
virtual float width() const { return m_wipe_tower_width; }
virtual float width() const { return m_wipe_tower_width; }
// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
virtual bool finished() const { return m_max_color_changes == 0; }
virtual bool finished() const { return m_max_color_changes == 0; }
// Returns gcode for a toolchange and a final print head position.
// On the first layer, extrude a brim around the future wipe tower first.
virtual std::pair<std::string, xy> tool_change(int new_tool, Purpose purpose);
virtual ToolChangeResult tool_change(int new_tool, Purpose purpose);
// Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag.
// Call this method only if layer_finished() is false.
virtual std::pair<std::string, xy> finish_layer(Purpose purpose);
virtual ToolChangeResult finish_layer(Purpose purpose);
// Is the current layer finished? A layer is finished if either the wipe tower is finished, or
// the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const
virtual bool layer_finished() const
{ return m_idx_tool_change_in_layer == m_max_color_changes; }
private:
@ -143,6 +147,8 @@ private:
float m_wipe_area;
// Current Z position.
float m_z_pos = 0.f;
// Current layer height.
float m_layer_height = 0.f;
// Maximum number of color changes per layer.
size_t m_max_color_changes = 0;
// Is this the 1st layer of the print? If so, print the brim around the waste tower.
@ -172,7 +178,7 @@ private:
unsigned int m_idx_tool_change_in_layer = 0;
// A fill-in direction (positive Y, negative Y) alternates with each layer.
wipe_shape m_current_shape = SHAPE_NORMAL;
material_type m_current_material = PLA;
unsigned int m_current_tool = 0;
// Current y position at the wipe tower.
float m_current_wipe_start_y = 0.f;
@ -210,7 +216,7 @@ private:
// Returns gcode for wipe tower brim
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
// offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower
std::pair<std::string, WipeTower::xy> toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f);
ToolChangeResult toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f);
void toolchange_Unload(
PrusaMultiMaterial::Writer &writer,

View file

@ -31,6 +31,8 @@ public:
SurfaceCollection slices;
// collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection thin_fills;
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")

View file

@ -5,6 +5,7 @@
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SupportMaterial.hpp"
#include "GCode/WipeTowerPrusaMM.hpp"
#include <algorithm>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
@ -117,7 +118,7 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
// this method only accepts PrintConfig option keys
for (std::vector<t_config_option_key>::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) {
if (*opt_key == "skirts"
if ( *opt_key == "skirts"
|| *opt_key == "skirt_height"
|| *opt_key == "skirt_distance"
|| *opt_key == "min_skirt_length"
@ -129,12 +130,26 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
} else if (*opt_key == "nozzle_diameter"
|| *opt_key == "resolution") {
osteps.insert(posSlice);
} else if (
*opt_key == "complete_objects"
|| *opt_key == "filament_type"
|| *opt_key == "first_layer_temperature"
|| *opt_key == "gcode_flavor"
|| *opt_key == "single_extruder_multi_material"
|| *opt_key == "spiral_vase"
|| *opt_key == "temperature"
|| *opt_key == "wipe_tower"
|| *opt_key == "wipe_tower_x"
|| *opt_key == "wipe_tower_y"
|| *opt_key == "wipe_tower_width"
|| *opt_key == "wipe_tower_per_color_wipe"
|| *opt_key == "z_offset") {
steps.insert(psWipeTower);
} else if (*opt_key == "avoid_crossing_perimeters"
|| *opt_key == "bed_shape"
|| *opt_key == "bed_temperature"
|| *opt_key == "bridge_acceleration"
|| *opt_key == "bridge_fan_speed"
|| *opt_key == "complete_objects"
|| *opt_key == "cooling"
|| *opt_key == "default_acceleration"
|| *opt_key == "disable_fan_first_layers"
@ -150,14 +165,11 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|| *opt_key == "fan_below_layer_time"
|| *opt_key == "filament_diameter"
|| *opt_key == "filament_notes"
|| *opt_key == "filament_type"
|| *opt_key == "filament_soluble"
|| *opt_key == "first_layer_acceleration"
|| *opt_key == "first_layer_bed_temperature"
|| *opt_key == "first_layer_speed"
|| *opt_key == "first_layer_temperature"
|| *opt_key == "gcode_comments"
|| *opt_key == "gcode_flavor"
|| *opt_key == "infill_acceleration"
|| *opt_key == "infill_first"
|| *opt_key == "layer_gcode"
@ -181,24 +193,15 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|| *opt_key == "retract_restart_extra_toolchange"
|| *opt_key == "retract_speed"
|| *opt_key == "deretract_speed"
|| *opt_key == "single_extruder_multi_material"
|| *opt_key == "slowdown_below_layer_time"
|| *opt_key == "spiral_vase"
|| *opt_key == "standby_temperature_delta"
|| *opt_key == "start_gcode"
|| *opt_key == "temperature"
|| *opt_key == "threads"
|| *opt_key == "toolchange_gcode"
|| *opt_key == "travel_speed"
|| *opt_key == "use_firmware_retraction"
|| *opt_key == "use_relative_e_distances"
|| *opt_key == "wipe"
|| *opt_key == "wipe_tower"
|| *opt_key == "wipe_tower_x"
|| *opt_key == "wipe_tower_y"
|| *opt_key == "wipe_tower_width"
|| *opt_key == "wipe_tower_per_color_wipe"
|| *opt_key == "z_offset"
|| *opt_key == "max_volumetric_extrusion_rate_slope_negative"
|| *opt_key == "max_volumetric_extrusion_rate_slope_positive") {
// these options only affect G-code export, so nothing to invalidate
@ -208,6 +211,7 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
osteps.insert(posSupportMaterial);
steps.insert(psSkirt);
steps.insert(psBrim);
steps.insert(psWipeTower);
} else {
// for legacy, if we can't handle this option let's invalidate all steps
return this->invalidate_all_steps();
@ -233,23 +237,22 @@ Print::invalidate_step(PrintStep step)
bool invalidated = this->state.invalidate(step);
// propagate to dependent steps
if (step == psSkirt) {
if (step == psSkirt)
this->invalidate_step(psBrim);
}
return invalidated;
}
bool
Print::invalidate_all_steps()
bool Print::invalidate_all_steps()
{
// make a copy because when invalidating steps the iterators are not working anymore
std::set<PrintStep> steps = this->state.started;
bool invalidated = false;
for (std::set<PrintStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
if (this->invalidate_step(*step)) invalidated = true;
}
for (PrintStep step : steps)
if (this->invalidate_step(step))
invalidated = true;
return invalidated;
}
@ -369,6 +372,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
// invalidate steps
this->invalidate_step(psSkirt);
this->invalidate_step(psBrim);
this->invalidate_step(psWipeTower);
}
}
@ -991,15 +995,119 @@ void Print::_make_skirt()
this->skirt.reverse();
}
std::string
Print::output_filename()
// Wipe tower support.
bool Print::has_wipe_tower()
{
return
this->config.single_extruder_multi_material.value &&
! this->config.spiral_vase.value &&
this->config.wipe_tower.value &&
this->config.nozzle_diameter.values.size() > 1;
}
void Print::_clear_wipe_tower()
{
m_tool_ordering.clear();
m_wipe_tower_tool_changes.clear();
m_wipe_tower_final_purge.reset(nullptr);
}
void Print::_make_wipe_tower()
{
this->_clear_wipe_tower();
if (! this->has_wipe_tower())
return;
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1);
unsigned int initial_extruder_id = m_tool_ordering.first_extruder();
if (initial_extruder_id == (unsigned int)-1)
return;
// Initialize the wipe tower.
WipeTowerPrusaMM wipe_tower(
float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value),
float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value),
initial_extruder_id);
//wipe_tower.set_retract();
//wipe_tower.set_zhop();
//wipe_tower.set_zhop();
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < 4; ++ i)
wipe_tower.set_extruder(
i,
WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()),
this->config.temperature.get_at(i),
this->config.first_layer_temperature.get_at(i));
// Generate the wipe tower layers.
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
unsigned int current_extruder_id = initial_extruder_id;
for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) {
if (! layer_tools.has_wipe_tower)
// This is a support only layer, or the wipe tower does not reach to this height.
continue;
bool first_layer = &layer_tools == &m_tool_ordering.front();
bool last_layer = &layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0;
wipe_tower.set_layer(
float(layer_tools.print_z),
float(first_layer ?
this->objects.front()->config.first_layer_height.get_abs_value(this->objects.front()->config.layer_height.value) :
this->objects.front()->config.layer_height.value),
layer_tools.wipe_tower_partitions,
first_layer,
last_layer);
std::vector<WipeTower::ToolChangeResult> tool_changes;
for (unsigned int extruder_id : layer_tools.extruders)
if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) {
tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, WipeTower::PURPOSE_EXTRUDE));
current_extruder_id = extruder_id;
}
if (! wipe_tower.layer_finished()) {
tool_changes.emplace_back(wipe_tower.finish_layer(WipeTower::PURPOSE_EXTRUDE));
if (tool_changes.size() > 1) {
// Merge the two last tool changes into one.
WipeTower::ToolChangeResult &tc1 = tool_changes[tool_changes.size() - 2];
WipeTower::ToolChangeResult &tc2 = tool_changes.back();
if (tc1.end_pos != tc2.start_pos) {
// Add a travel move from tc1.end_pos to tc2.start_pos.
char buf[2048];
sprintf(buf, "G1 X%.3f Y%.3f F7200\n", tc2.start_pos.x, tc2.start_pos.y);
tc1.gcode += buf;
}
tc1.gcode += tc2.gcode;
append(tc1.extrusions, tc2.extrusions);
tc1.end_pos = tc2.end_pos;
tool_changes.pop_back();
}
}
m_wipe_tower_tool_changes.emplace_back(std::move(tool_changes));
if (last_layer)
break;
}
// Tower is printed to the top and it has no empty space for the final extruder purge.
bool tower_full = m_tool_ordering.back().wipe_tower_partitions > 0 && wipe_tower.layer_finished();
coordf_t last_print_z = m_tool_ordering.back().print_z;
// Unload the current filament over the purge tower.
if (tower_full) {
// There is not enough space on the wipe tower to purge the nozzle into. Lift Z to the next layer.
coordf_t layer_height = this->objects.front()->config.layer_height.value;
wipe_tower.set_layer(float(last_print_z + layer_height), float(layer_height), 0, false, true);
}
m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
wipe_tower.tool_change(-1, WipeTower::PURPOSE_EXTRUDE));
}
std::string Print::output_filename()
{
this->placeholder_parser.update_timestamp();
return this->placeholder_parser.process(this->config.output_filename_format.value);
}
std::string
Print::output_filepath(const std::string &path)
std::string Print::output_filepath(const std::string &path)
{
// if we were supplied no path, generate an automatic one based on our first object's input file
if (path.empty()) {

View file

@ -13,6 +13,8 @@
#include "Model.hpp"
#include "PlaceholderParser.hpp"
#include "Slicing.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
namespace Slic3r {
@ -22,7 +24,7 @@ class ModelObject;
// Print step IDs for keeping track of the print state.
enum PrintStep {
psSkirt, psBrim,
psSkirt, psBrim, psWipeTower
};
enum PrintObjectStep {
posSlice, posPerimeters, posPrepareInfill,
@ -258,6 +260,18 @@ public:
void auto_assign_extruders(ModelObject* model_object) const;
void _make_skirt();
// Wipe tower support.
bool has_wipe_tower();
void _clear_wipe_tower();
void _make_wipe_tower();
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
// Cache it here, so it does not need to be recalculated during the G-code generation.
ToolOrdering m_tool_ordering;
// Cache of tool changes per print layer.
std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge;
std::string output_filename();
std::string output_filepath(const std::string &path);

View file

@ -316,17 +316,21 @@ PrintObject::invalidate_step(PrintObjectStep step)
this->invalidate_step(posPrepareInfill);
this->_print->invalidate_step(psSkirt);
this->_print->invalidate_step(psBrim);
this->_print->invalidate_step(psWipeTower);
} else if (step == posPrepareInfill) {
this->invalidate_step(posInfill);
} else if (step == posInfill) {
this->_print->invalidate_step(psSkirt);
this->_print->invalidate_step(psBrim);
this->_print->invalidate_step(psWipeTower);
} else if (step == posSlice) {
this->invalidate_step(posPerimeters);
this->invalidate_step(posSupportMaterial);
this->_print->invalidate_step(psWipeTower);
} else if (step == posSupportMaterial) {
this->_print->invalidate_step(psSkirt);
this->_print->invalidate_step(psBrim);
this->_print->invalidate_step(psWipeTower);
}
return invalidated;
@ -1055,7 +1059,7 @@ void PrintObject::_slice()
{
BOOST_LOG_TRIVIAL(info) << "Slicing objects...";
#if 0
#if 1
// Disable parallelization for debugging purposes.
static tbb::task_scheduler_init *tbb_init = nullptr;
tbb_init = new tbb::task_scheduler_init(1);

View file

@ -962,4 +962,148 @@ void _3DScene::_load_print_object_toolpaths(
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end";
}
void _3DScene::_load_wipe_tower_toolpaths(
const Print *print,
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors_str,
bool use_VBOs)
{
std::vector<float> tool_colors = parse_colors(tool_colors_str);
struct Ctxt
{
const Print *print;
const std::vector<float> *tool_colors;
// Number of vertices (each vertex is 6x4=24 bytes long)
static const size_t alloc_size_max () { return 131072; } // 3.15MB
static const size_t alloc_size_reserve() { return alloc_size_max() * 2; }
static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
// For cloring by a tool, return a parsed color.
bool color_by_tool() const { return tool_colors != nullptr; }
size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; }
const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; }
} ctxt;
ctxt.print = print;
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
//FIXME Improve the heuristics for a grain size.
size_t n_layers = print->m_wipe_tower_tool_changes.size();
size_t grain_size = std::max(n_layers / 128, size_t(1));
tbb::spin_mutex new_volume_mutex;
auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
auto *volume = new GLVolume(color);
new_volume_mutex.lock();
volumes->volumes.emplace_back(volume);
new_volume_mutex.unlock();
return volume;
};
const size_t volumes_cnt_initial = volumes->volumes.size();
std::vector<GLVolumeCollection> volumes_per_thread(n_layers);
tbb::parallel_for(
tbb::blocked_range<size_t>(0, n_layers, grain_size),
[&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
// Bounding box of this slab of a wipe tower.
BoundingBoxf3 bbox;
bbox.min = Pointf3(
ctxt.print->config.wipe_tower_x.value - 10.,
ctxt.print->config.wipe_tower_y.value - 10.,
ctxt.print->m_wipe_tower_tool_changes[range.begin()].front().print_z - 3.);
bbox.max = Pointf3(
ctxt.print->config.wipe_tower_x.value + ctxt.print->config.wipe_tower_width.value + 10.,
ctxt.print->config.wipe_tower_y.value + ctxt.print->config.wipe_tower_per_color_wipe.value *
ctxt.print->m_tool_ordering.layer_tools()[range.begin()].wipe_tower_partitions + 10.,
ctxt.print->m_wipe_tower_tool_changes[range.end() - 1].front().print_z + 0.1);
std::vector<GLVolume*> vols;
if (ctxt.color_by_tool()) {
for (size_t i = 0; i < ctxt.number_tools(); ++ i)
vols.emplace_back(new_volume(ctxt.color_tool(i)));
} else
vols = { new_volume(ctxt.color_support()) };
for (size_t i = 0; i < vols.size(); ++ i) {
GLVolume &volume = *vols[i];
volume.bounding_box = bbox;
volume.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
}
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.print->m_wipe_tower_tool_changes[idx_layer];
coordf_t layer_height = (idx_layer == 0) ?
ctxt.print->objects.front()->config.first_layer_height.get_abs_value(ctxt.print->objects.front()->config.layer_height.value) :
ctxt.print->objects.front()->config.layer_height.value;
for (size_t i = 0; i < vols.size(); ++ i) {
GLVolume &vol = *vols[i];
if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
vol.print_zs.push_back(layer.front().print_z);
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
}
}
for (const WipeTower::ToolChangeResult &extrusions : layer) {
for (size_t i = 1; i < extrusions.extrusions.size();) {
const WipeTower::Extrusion &e = extrusions.extrusions[i];
if (e.width == 0.) {
++ i;
continue;
}
size_t j = i + 1;
if (ctxt.color_by_tool())
for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++ j) ;
else
for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++ j) ;
size_t n_lines = j - i;
Lines lines;
std::vector<double> widths;
std::vector<double> heights;
lines.reserve(n_lines);
widths.reserve(n_lines);
heights.assign(n_lines, layer_height);
for (; i < j; ++ i) {
const WipeTower::Extrusion &e = extrusions.extrusions[i];
assert(e.width > 0.f);
const WipeTower::Extrusion &e_prev = *(&e - 1);
lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
widths.emplace_back(e.width);
}
thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.color_by_tool() ? e.tool : 0]);
}
}
}
for (size_t i = 0; i < vols.size(); ++ i) {
GLVolume &vol = *vols[i];
if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
// Store the vertex arrays and restart their containers,
vols[i] = new_volume(vol.color);
GLVolume &vol_new = *vols[i];
vol_new.bounding_box = bbox;
// Assign the large pre-allocated buffers to the new GLVolume.
vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
// Copy the content back to the old GLVolume.
vol.indexed_vertex_array = vol_new.indexed_vertex_array;
// Clear the buffers, but keep them pre-allocated.
vol_new.indexed_vertex_array.clear();
// Just make sure that clear did not clear the reserved memory.
vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
}
}
for (size_t i = 0; i < vols.size(); ++ i)
vols[i]->indexed_vertex_array.shrink_to_fit();
});
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results";
// Remove empty volumes from the newly added volumes.
volumes->volumes.erase(
std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(),
[](const GLVolume *volume) { return volume->empty(); }),
volumes->volumes.end());
for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i)
volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs);
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end";
}
}

View file

@ -354,6 +354,13 @@ public:
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors,
bool use_VBOs);
static void _3DScene::_load_wipe_tower_toolpaths(
const Print *print,
GLVolumeCollection *volumes,
const std::vector<std::string> &tool_colors_str,
bool use_VBOs);
};
}

View file

@ -135,4 +135,13 @@ _load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs)
CODE:
_3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0);
void
_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs)
Print *print;
GLVolumeCollection *volumes;
std::vector<std::string> tool_colors;
int use_VBOs;
CODE:
_3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
%}

View file

@ -19,6 +19,7 @@ _constant()
STEP_SUPPORTMATERIAL = posSupportMaterial
STEP_SKIRT = psSkirt
STEP_BRIM = psBrim
STEP_WIPE_TOWER = psWipeTower
PROTOTYPE:
CODE:
RETVAL = ix;
@ -238,6 +239,11 @@ _constant()
Clone<Flow> skirt_flow();
void _make_skirt();
bool has_wipe_tower();
void _clear_wipe_tower();
void _make_wipe_tower();
%{
double