Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
92 changes: 76 additions & 16 deletions src/libslic3r/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,11 +632,24 @@ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::T
double current_z = gcodegen.writer().get_position().z();
if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
if (!is_approx(z, current_z)) {
// Orca: wipe_tower_no_sparse_layers crash guard. With sparse layers skipped the tower is
// compacted far below the object, so descending to it is only safe once the nozzle is parked
// over the tower - which is what the is_finish_first travel above does. Otherwise the nozzle
// is still over the model and this descent would drive it into the print, so defer it to the
// re-descents below, which run after the travel to the tower.
// Orca #15441: will_go_down also gates restoring layer Z around change_filament_gcode, so
// custom toolchange travel cannot sweep through parts taller than the compacted tower.
const bool will_go_down = !is_approx(z, current_z);
const bool defer_compacted_descend = m_sparse_layers_skipped
&& !tcr.priming && !tcr.is_finish_first && (current_z - z) > EPSILON;
if (will_go_down && !defer_compacted_descend) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
gcode += gcodegen.writer().unretract();
}
// Tower compacted below the object, so any extrusion emitted without an explicit z has to be
// pulled back down to it first.
const bool compacted_below_object = m_sparse_layers_skipped && z >= 0. && (tcr.print_z - z) > EPSILON;

// Process the end filament gcode.
std::string end_filament_gcode_str;
Expand Down Expand Up @@ -682,6 +695,23 @@ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::T
std::string toolchange_retract_str = gcodegen.retract(true, false);
check_add_eol(toolchange_retract_str);

// Orca #15441: the wipe tower can currently be printing below the topmost printed layer
// (e.g. wipe_tower_no_sparse_layers keeps the tower shorter than the tallest object).
// change_filament_gcode is free to perform arbitrary XY travel (parking, homing,
// docking a tool, etc.), so restore Z to the real layer height before running it,
// otherwise that travel can collide with already-printed parts taller than the tower.
// toolchange_retract_str above already keeps us retracted for the whole toolchange, so
// just move Z; deretraction_str (further below) brings Z back down to resume the tower.
std::string restore_layer_z_str;
if (will_go_down) {
restore_layer_z_str += gcodegen.writer().travel_to_z(current_z,
"Restore layer Z before toolchange to avoid collision with printed parts", true);
Vec3d position{gcodegen.writer().get_position()};
position.z() = current_z;
gcodegen.writer().set_position(position);
check_add_eol(restore_layer_z_str);
}

// Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament.
// Otherwise, leave control to the user completely.
std::string toolchange_gcode_str;
Expand Down Expand Up @@ -852,6 +882,17 @@ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::T
check_add_eol(toolchange_gcode_str);
}

// Orca #15441: mirror of restore_layer_z_str above - bring Z back down to the wipe tower layer
// once change_filament_gcode has finished, so the rest of the tower prints at the right height.
std::string deretraction_str;
if (will_go_down) {
deretraction_str += gcodegen.writer().travel_to_z(z, "Restore wipe tower layer Z after toolchange", true);
Vec3d position{gcodegen.writer().get_position()};
position.z() = z;
gcodegen.writer().set_position(position);
check_add_eol(deretraction_str);
}

std::string toolchange_command;
if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
Expand Down Expand Up @@ -879,10 +920,21 @@ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::T
check_add_eol(start_filament_gcode_str);
}

// Orca: the custom change_filament_gcode lifts to the object layer height and the unretract
// de-hops back to it, so every tower extrusion emitted after it (purge moves, and the wall
// when it prints after the toolchange) would float above the compacted tower. Descend first.
if (compacted_below_object) {
std::string z_descend = gcodegen.writer().travel_to_z(z, "Descend to compacted wipe tower z (no sparse layers)");
check_add_eol(z_descend);
start_filament_gcode_str += z_descend;
}

// Insert the end filament, toolchange, and start filament gcode into the generated gcode.
DynamicConfig config;
config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str));
config.set_key_value("restore_layer_z_before_toolchange", new ConfigOptionString(restore_layer_z_str));
config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str));
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str));
std::string tcr_gcode,
tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
Expand Down Expand Up @@ -1011,9 +1063,23 @@ std::string WipeTowerIntegration::append_tcr2(GCode& gcodegen, const WipeTower::

std::string toolchange_gcode_str;
std::string deretraction_str;
std::string restore_layer_z_str;
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
if (is_ramming)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
// Orca #15441: the wipe tower can currently be printing below the topmost printed layer
// (e.g. wipe_tower_no_sparse_layers keeps the tower shorter than the tallest object).
// change_filament_gcode is free to perform arbitrary XY travel (parking, homing,
// docking a tool, etc.), so restore Z to the real layer height before running it,
// otherwise that travel can collide with already-printed parts taller than the tower.
// deretraction_str (below) brings Z back down to resume printing the wipe tower.
if (will_go_down && gcodegen.config().enable_prime_tower) {
restore_layer_z_str += gcodegen.writer().retract();
restore_layer_z_str += gcodegen.writer().travel_to_z(current_z,
"Restore layer Z before toolchange to avoid collision with printed parts");
restore_layer_z_str += gcodegen.writer().unretract();
check_add_eol(restore_layer_z_str);
}
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
if (gcodegen.config().enable_prime_tower) {
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
Expand All @@ -1029,6 +1095,7 @@ std::string WipeTowerIntegration::append_tcr2(GCode& gcodegen, const WipeTower::
DynamicConfig config;
config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str));
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
config.set_key_value("restore_layer_z_before_toolchange", new ConfigOptionString(restore_layer_z_str));
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
Expand Down Expand Up @@ -1453,11 +1520,9 @@ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id,
// resulting in a wipe tower with sparse layers.
double wipe_tower_z = -1;
bool ignore_sparse = false;
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
if (m_sparse_layers_skipped) {
wipe_tower_z = m_last_wipe_tower_print_z;
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 &&
m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool &&
m_layer_idx != 0);
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]) && m_layer_idx != 0;
if (m_tool_change_idx == 0 && !ignore_sparse)
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
}
Expand All @@ -1474,12 +1539,10 @@ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id,
// resulting in a wipe tower with sparse layers.
double wipe_tower_z = -1;
bool ignore_sparse = false;
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
wipe_tower_z = m_last_wipe_tower_print_z;
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 &&
m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
if (m_tool_change_idx == 0 && !ignore_sparse)
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
if (m_sparse_layers_skipped) {
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);
if (size_t(m_layer_idx) < m_compacted_tower_z.size())
wipe_tower_z = m_compacted_tower_z[m_layer_idx];
}

if (m_enable_timelapse_print && m_is_first_print) {
Expand All @@ -1495,7 +1558,6 @@ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id,
if (!ignore_sparse) {
realign_nominal_toolchange_idx(extruder_id);
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
m_last_wipe_tower_print_z = wipe_tower_z;
}
}
}
Expand All @@ -1512,10 +1574,8 @@ bool WipeTowerIntegration::is_empty_wipe_tower_gcode(GCode& gcodegen, int extrud
return true;

bool ignore_sparse = false;
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 &&
m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
}
if (m_sparse_layers_skipped)
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);

if (m_enable_timelapse_print && m_is_first_print) {
return false;
Expand Down
18 changes: 15 additions & 3 deletions src/libslic3r/GCode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,15 @@ class WipeTowerIntegration {
m_plate_origin(plate_origin),
m_single_extruder_multi_material(print_config.single_extruder_multi_material),
m_enable_timelapse_print(print_config.timelapse_type.value == TimelapseType::tlSmooth),
m_is_first_print(true)
{}
m_is_first_print(true),
m_last_wipe_tower_print_z(print_config.z_offset.value),
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(print_config))
{
// Precomputed rather than accumulated while emitting, so that the clearance validator and
// the emitter cannot disagree about where the compacted tower sits on any given layer.
if (m_sparse_layers_skipped)
m_compacted_tower_z = compute_compacted_wipe_tower_z(tool_changes, float(print_config.z_offset.value));
}

std::string prime(GCode &gcodegen);
void next_layer() {
Expand Down Expand Up @@ -153,13 +160,18 @@ class WipeTowerIntegration {
int m_tool_change_idx;
std::vector<size_t> m_local_z_tool_change_idx;
std::vector<size_t> m_local_z_reserve_slot_idx;
double m_last_wipe_tower_print_z = 0.f;

// BBS
Vec3d m_plate_origin;
bool m_single_extruder_multi_material;
bool m_enable_timelapse_print;
bool m_is_first_print;
double m_last_wipe_tower_print_z = 0.f;
// wipe_tower_no_sparse_layers, as answered by the shared compaction rule rather than by the raw
// option: smooth timelapse keeps a tower on every layer regardless.
const bool m_sparse_layers_skipped;
// Print z of the compacted tower per planned layer. Empty when the tower is not compacted.
std::vector<float> m_compacted_tower_z;
};

class ColorPrintColors
Expand Down
41 changes: 37 additions & 4 deletions src/libslic3r/GCode/WipeTower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,36 @@
#include "GCodeProcessor.hpp"
#include "BoundingBox.hpp"
#include "LocalesUtils.hpp"
#include "PrintConfig.hpp"
#include "Triangulation.hpp"


namespace Slic3r
{

bool wipe_tower_sparse_layers_skipped(const PrintConfig &config)
{
return config.wipe_tower_no_sparse_layers.value && config.timelapse_type.value != TimelapseType::tlSmooth;
}

bool wipe_tower_layer_is_sparse(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes)
{
return layer_tool_changes.size() == 1 && layer_tool_changes.front().initial_tool == layer_tool_changes.front().new_tool;
}

std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
float base_z)
{
std::vector<float> tower_z(tool_changes.size(), base_z);
float last = base_z;
for (size_t i = 0; i < tool_changes.size(); ++i) {
if (! tool_changes[i].empty() && ! wipe_tower_layer_is_sparse(tool_changes[i]))
last += tool_changes[i].front().layer_height;
tower_z[i] = last;
}
return tower_z;
}

static const double wipe_tower_wall_infill_overlap = 0.0;

inline float align_round(float value, float base)
Expand Down Expand Up @@ -781,7 +806,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
m_z_pos(0.f),
//m_bridging(float(config.wipe_tower_bridging)),
m_bridging(10.f),
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
m_gcode_flavor(config.gcode_flavor),
m_has_nozzle_rack(Slic3r::has_nozzle_rack(config)),
m_travel_speed(config.travel_speed),
Expand Down Expand Up @@ -1177,7 +1202,15 @@ void WipeTower::toolchange_Change(
// This is where we want to place the custom gcodes. We will use placeholders for this.
// These will be substituted by the actual gcodes when the gcode is generated.
writer.append("[filament_end_gcode]\n");
// Orca #15441: restore Z to the real topmost printed layer before running change_filament_gcode.
// The wipe tower can be printing below that height (e.g. wipe_tower_no_sparse_layers), and
// change_filament_gcode is free to travel anywhere on the bed, so it must not run while the
// toolhead sits lower than already-printed parts. See append_tcr() in GCode.cpp.
writer.append("[restore_layer_z_before_toolchange]\n");
writer.append("[change_filament_gcode]\n");
// Orca #15441: bring Z back down to the wipe tower layer once change_filament_gcode has finished,
// so the rest of the tower (filament_start_gcode, wipe, ...) prints at the right height.
writer.append("[deretraction_from_wipe_tower_generator]\n");

// BBS: do travel in GCode::append_tcr() for lazy_lift
#if 0
Expand Down Expand Up @@ -1521,7 +1554,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool

// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (! m_no_sparse_layers || toolchanges_on_layer)
if (! m_sparse_layers_skipped || toolchanges_on_layer)
if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();

Expand All @@ -1537,7 +1570,7 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first
m_plan.push_back(WipeTowerInfo(z_par, layer_height_par));

if (m_first_layer_idx == size_t(-1) && (! m_no_sparse_layers || old_tool != new_tool))
if (m_first_layer_idx == size_t(-1) && (! m_sparse_layers_skipped || old_tool != new_tool))
m_first_layer_idx = m_plan.size() - 1;

if (old_tool == new_tool) // new layer without toolchanges - we are done
Expand Down Expand Up @@ -1900,7 +1933,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall()

// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (!m_no_sparse_layers || toolchanges_on_layer)
if (!m_sparse_layers_skipped || toolchanges_on_layer)
if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();

return construct_tcr(writer, false, old_tool, true, 0.f);
Expand Down
22 changes: 21 additions & 1 deletion src/libslic3r/GCode/WipeTower.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ class WipeTower
float m_parking_pos_retraction = 0.f;
float m_extra_loading_move = 0.f;
float m_bridging = 0.f;
bool m_no_sparse_layers = false;
bool m_sparse_layers_skipped = false;
bool m_set_extruder_trimpot = false;
bool m_adhesion = true;
GCodeFlavor m_gcode_flavor;
Expand Down Expand Up @@ -467,6 +467,26 @@ class WipeTower
};


// Compaction rule for wipe_tower_no_sparse_layers. Shared by the G-code emitter and by the
// clearance validator so that both agree on where the compacted tower actually sits; a drift
// between the two would either let a real nozzle collision through or reject a safe plate.

// Whether sparse layers are really skipped, i.e. whether the tower is compacted at all. Smooth
// timelapse puts a tower on every layer, so no layer is ever dropped and the tower keeps following
// the object even though the option is on. Tower planning, G-code emission and the clearance
// validator all ask this single question, so none of them can compact on its own.
bool wipe_tower_sparse_layers_skipped(const PrintConfig &config);

// A planned layer prints no tower at all when its only toolchange keeps the same filament.
bool wipe_tower_layer_is_sparse(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes);

// Print z the compacted tower reaches on every planned layer. Sparse layers carry over the
// previous value, so the tower falls one layer height behind the object for each of them. base_z is
// the z the tower starts from, which Orca offsets by z_offset.
std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
float base_z = 0.f);




} // namespace Slic3r
Expand Down
Loading
Loading