init work to integrate OrcaSlicer-FullSpectrum fork

Integrations up to commit b3c41fda41.

  - libslic3r: vendor FilamentMixer; MixedFilamentManager (auto-gen, resolve,
    serialize; manual-pattern / gradient / pointillism); 19 new PrintConfig
    keys; PresetBundle owns the canonical manager with 3MF + AppConfig
    roundtrip and AMS-safe strip+restore; Print owns the slicing-time copy
    with PrintApply auto-regen on color change; TriangleSelector::
    shift_states_above + filament-id remap; inset_idx propagation through
    ExtrusionPath/Loop/MultiPath copy/assign.
  - Slicing: virtual filament IDs in painted regions (same-physical channels
    collapse when mixed_filament_region_collapse is on); ByObject
    collect_filament_data expands mixed slots; pair-cadence + whole-object +
    3+component Local-Z plan generators; LocalZOrderOptimizer utility.
  - GCode + ToolOrdering: LayerTools resolves virtual IDs through wall /
    infill / sparse / solid queries; SameLayerPointillisme in process_layer
    (uniform-segment + grouped per-perimeter-index splitters); WipeTower2
    Local-Z reservation + sub-layer G-code emission; per-layer infill
    filament override.
  - PartPlate: get_extruders* expand virtual slots into physical components;
    CLI path rebuilds a local manager from full_config.
  - GUI: five widget files extracted from FS Plater.cpp (~5000 LOC) —
    MixedMixPreview, MixedGradientSelector + WeightsDialog,
    MixedFilamentColorMapPanel, MixedFilamentColorMatchDialog (ΔE₀₀ recipe
    search), MixedFilamentConfigPanel; Sidebar Mixed Filaments panel
    (drag-reorder, enable/delete, Add Gradient/Pattern/Color); Tab exposure
    of mixed-filament / dithering / per-layer infill-override settings +
    ConfigManipulation visibility and slot-validation rules;
    BBLMixedFilamentBroken / BBLSingleExtruderMixedFilamentRisk
    notifications + slice gate; WipeTowerDialog edits physical P×P
    sub-matrix; bounds-safe extruder_id guards in 3DScene / GLCanvas3D /
    GLGizmoMmuSegmentation; change_filament merge guard and
    on_filaments_delete is_mixed_before_delete propagation.
  - Tests: 4 Catch2 tests for 3MF roundtrip (auto/custom persistence,
    PresetBundle string path, total_filaments stability); full-pipeline
    slice E2E deferred — TODO in file.

  Co-authored-by: Rad <radugheorghiu96@gmail.com>
  Co-authored-by: Justin Hayes <justinh@rahb.ca>
  Co-authored-by: Calogero Guagenti <calogeroguagenti@gmail.com>
  Co-authored-by: xSil3nt <ahmedshazin21@gmail.com>
  Co-authored-by: ratdoux <62392831+ratdoux@users.noreply.github.com>

Co-authored-by: Rad <radugheorghiu96@gmail.com>
Co-authored-by: Justin Hayes <justinh@rahb.ca>
Co-authored-by: Calogero Guagenti <calogeroguagenti@gmail.com>
Co-authored-by: xSil3nt <ahmedshazin21@gmail.com>
Co-authored-by: ratdoux <62392831+ratdoux@users.noreply.github.com>
This commit is contained in:
SoftFever
2026-04-29 00:42:48 +08:00
parent e66ec7fd70
commit 9c8caf121e
68 changed files with 18988 additions and 267 deletions

View File

@@ -20,6 +20,7 @@
#include <cassert>
#include <limits>
#include <algorithm>
#include <cmath>
#include <unordered_map>
#include <libslic3r.h>
@@ -36,6 +37,113 @@ namespace Slic3r {
const static bool g_wipe_into_objects = false;
constexpr double similar_color_threshold_de2000 = 20.0;
// ---------------------------------------------------------------------------
// Anonymous-namespace helpers for mixed-filament resolution
// ---------------------------------------------------------------------------
namespace {
// Resolve a 1-based filament ID through the mixed-filament manager, taking the
// optional per-object layer-height cadence (a/b) into account.
// Returns the resolved 1-based physical ID, or the input unchanged when the ID
// is not a mixed slot.
unsigned int resolve_mixed_with_layer_heights(const MixedFilamentManager *mixed_mgr,
size_t num_physical,
unsigned int filament_id_1based,
int layer_index,
float layer_print_z,
float layer_height,
float layer_height_a,
float layer_height_b,
float base_layer_height)
{
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
return filament_id_1based;
const MixedFilament *mixed_row = mixed_mgr->mixed_filament_from_id(filament_id_1based, num_physical);
const bool is_custom_mixed = mixed_row != nullptr && mixed_row->custom;
if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) {
const float safe_base = std::max<float>(0.01f, base_layer_height);
const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base)));
const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base)));
const int cycle = ratio_a + ratio_b;
if (cycle > 0 && mixed_row != nullptr) {
const int pos = ((layer_index % cycle) + cycle) % cycle;
return pos < ratio_a ? mixed_row->component_a : mixed_row->component_b;
}
}
return mixed_mgr->resolve(filament_id_1based, num_physical, layer_index, layer_print_z, layer_height);
}
bool has_grouped_manual_pattern(const MixedFilamentManager *mixed_mgr,
size_t num_physical,
unsigned int filament_id_1based)
{
if (!(mixed_mgr && mixed_mgr->is_mixed(filament_id_1based, num_physical)))
return false;
const MixedFilament *mixed_row = mixed_mgr->mixed_filament_from_id(filament_id_1based, num_physical);
if (mixed_row == nullptr)
return false;
const std::string normalized = MixedFilamentManager::normalize_manual_pattern(mixed_row->manual_pattern);
return normalized.find(',') != std::string::npos;
}
bool internal_solid_infill_uses_sparse_filament(const PrintRegion &region, ExtrusionRole role)
{
return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON;
}
bool use_base_infill_filament_impl(const LayerTools &layer_tools, const PrintRegion &region)
{
const PrintRegionConfig &config = region.config();
if (!config.enable_infill_filament_override.value)
return true;
if (layer_tools.object_layer_count <= 0)
return false;
const int first_layers = std::max(0, config.infill_filament_use_base_first_layers.value);
const int last_layers = std::max(0, config.infill_filament_use_base_last_layers.value);
return layer_tools.layer_index < first_layers || layer_tools.layer_index >= layer_tools.object_layer_count - last_layers;
}
unsigned int sparse_infill_filament_id_1based_impl(const LayerTools &layer_tools, const PrintRegion &region)
{
return use_base_infill_filament_impl(layer_tools, region) ? region.config().wall_filament.value : region.config().sparse_infill_filament.value;
}
unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools& layer_tools,
unsigned int configured_filament_id_1based)
{
if (layer_tools.mixed_mgr == nullptr || layer_tools.num_physical == 0)
return 0;
if (has_grouped_manual_pattern(layer_tools.mixed_mgr, layer_tools.num_physical, configured_filament_id_1based))
return configured_filament_id_1based;
return 0;
}
unsigned int grouped_manual_pattern_infill_filament_1based(const LayerTools& layer_tools,
const PrintRegion& region,
unsigned int configured_filament_id_1based)
{
const unsigned int grouped_id =
grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_filament_id_1based);
if (grouped_id == 0)
return 0;
const int innermost_perimeter_index = std::max(0, region.config().wall_loops.value - 1);
return layer_tools.mixed_mgr->resolve_perimeter(grouped_id,
layer_tools.num_physical,
layer_tools.layer_index,
innermost_perimeter_index,
float(layer_tools.print_z),
float(layer_tools.layer_height));
}
} // anonymous namespace
// ---------------------------------------------------------------------------
static std::set<int>get_filament_by_type(const std::vector<unsigned int>& used_filaments, const PrintConfig* print_config, const std::string& type)
{
std::set<int> target_filaments;
@@ -79,23 +187,50 @@ bool check_filament_printable_after_group(const std::vector<unsigned int> &used_
return true;
}
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id_1based) const
{
if (!mixed_mgr || filament_id_1based == 0)
return filament_id_1based;
return resolve_mixed_with_layer_heights(mixed_mgr,
num_physical,
filament_id_1based,
this->layer_index,
static_cast<float>(this->print_z),
static_cast<float>(this->layer_height),
mixed_layer_height_a,
mixed_layer_height_b,
mixed_base_layer_height);
}
// Return a zero based extruder from the region, or extruder_override if overriden.
unsigned int LayerTools::wall_filament(const PrintRegion &region) const
{
assert(region.config().wall_filament.value > 0);
return ((this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override) - 1;
unsigned int id_1based = (this->extruder_override == 0)
? region.config().wall_filament.value
: this->extruder_override;
return resolve_mixed_1based(id_1based) - 1;
}
unsigned int LayerTools::sparse_infill_filament(const PrintRegion &region) const
{
assert(region.config().sparse_infill_filament.value > 0);
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override) - 1;
unsigned int id_1based = (this->extruder_override == 0)
? sparse_infill_filament_id_1based_impl(*this, region)
: this->extruder_override;
const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id_1based);
return ((grouped != 0) ? grouped : resolve_mixed_1based(id_1based)) - 1;
}
unsigned int LayerTools::solid_infill_filament(const PrintRegion &region) const
{
assert(region.config().solid_infill_filament.value > 0);
return ((this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override) - 1;
unsigned int id_1based = (this->extruder_override == 0)
? region.config().solid_infill_filament.value
: this->extruder_override;
const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id_1based);
return ((grouped != 0) ? grouped : resolve_mixed_1based(id_1based)) - 1;
}
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
@@ -104,20 +239,29 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c
assert(region.config().wall_filament.value > 0);
assert(region.config().sparse_infill_filament.value > 0);
assert(region.config().solid_infill_filament.value > 0);
// 1 based extruder ID.
unsigned int extruder = 1;
if (this->extruder_override == 0) {
if (extrusions.has_infill()) {
if (extrusions.has_solid_infill())
extruder = region.config().solid_infill_filament;
else
extruder = region.config().sparse_infill_filament;
} else
extruder = region.config().wall_filament.value;
} else
extruder = this->extruder_override;
if (extrusions.has_infill()) {
const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role();
if (internal_solid_infill_uses_sparse_filament(region, role))
return sparse_infill_filament(region);
return is_solid_infill(role) ? solid_infill_filament(region) : sparse_infill_filament(region);
}
return wall_filament(region);
}
return (extruder == 0) ? 0 : extruder - 1;
unsigned int LayerTools::sparse_infill_filament_id_1based(const PrintRegion &region) const
{
return sparse_infill_filament_id_1based_impl(*this, region);
}
unsigned int LayerTools::infill_filament_id_1based(const PrintRegion &region) const
{
// Default role: erInternalInfill routes through sparse path.
return sparse_infill_filament_id_1based_impl(*this, region);
}
bool LayerTools::use_base_infill_filament(const PrintRegion &region) const
{
return use_base_infill_filament_impl(*this, region);
}
static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height)
@@ -384,6 +528,10 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
m_print_full_config = &object.print()->full_print_config();
m_print_object_ptr = &object;
m_print = const_cast<Print*>(object.print());
// Mixed filament support.
m_mixed_mgr = &object.print()->mixed_filament_manager();
m_num_physical = object.print()->config().filament_colour.values.size();
update_mixed_layer_height_settings();
if (object.layers().empty())
return;
@@ -429,6 +577,10 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
m_print_full_config = &print.full_print_config();
m_print = const_cast<Print *>(&print); // for update the context of print
m_print_config_ptr = &print.config();
// Mixed filament support.
m_mixed_mgr = &print.mixed_filament_manager();
m_num_physical = print.config().filament_colour.values.size();
update_mixed_layer_height_settings();
// Initialize the print layers for all objects and all layers.
coordf_t max_layer_height = 0.;
@@ -481,6 +633,33 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->mark_skirt_layers(print.config(), max_layer_height);
}
void ToolOrdering::update_mixed_layer_height_settings()
{
const PrintConfig *cfg = m_print_config_ptr;
if (cfg == nullptr && m_print_object_ptr != nullptr)
cfg = &m_print_object_ptr->print()->config();
m_mixed_layer_height_a = 0.f;
m_mixed_layer_height_b = 0.f;
if (m_print_full_config != nullptr &&
m_print_full_config->has("mixed_color_layer_height_a") &&
m_print_full_config->has("mixed_color_layer_height_b")) {
m_mixed_layer_height_a = float(m_print_full_config->opt_float("mixed_color_layer_height_a"));
m_mixed_layer_height_b = float(m_print_full_config->opt_float("mixed_color_layer_height_b"));
} else if (cfg != nullptr) {
m_mixed_layer_height_a = cfg->mixed_color_layer_height_a.value;
m_mixed_layer_height_b = cfg->mixed_color_layer_height_b.value;
}
float base_height = 0.2f;
if (m_print_object_ptr != nullptr)
base_height = float(m_print_object_ptr->config().layer_height.value);
else if (m_print_full_config != nullptr && m_print_full_config->has("layer_height"))
base_height = float(m_print_full_config->opt_float("layer_height"));
m_mixed_base_layer_height = std::max<float>(0.01f, base_height);
}
static void apply_first_layer_order(const DynamicPrintConfig* config, std::vector<unsigned int>& tool_order) {
const ConfigOptionInts* first_layer_print_sequence_op = config->option<ConfigOptionInts>("first_layer_print_sequence");
if (first_layer_print_sequence_op) {
@@ -646,6 +825,15 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
{
// Propagate mixed-filament context to all LayerTools entries.
for (LayerTools &lt : m_layer_tools) {
lt.mixed_mgr = m_mixed_mgr;
lt.num_physical = m_num_physical;
lt.mixed_layer_height_a = m_mixed_layer_height_a;
lt.mixed_layer_height_b = m_mixed_layer_height_b;
lt.mixed_base_layer_height = m_mixed_base_layer_height;
}
// Extruder overrides are ordered by print_z.
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
@@ -659,6 +847,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
// Collect the object extruders.
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
// Store the sequential layer index and height for mixed-filament resolution.
layer_tools.layer_index = layerCount;
layer_tools.object_layer_count = static_cast<int>(object.layers().size());
layer_tools.layer_height = layer->height;
// Override extruder with the next
for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
@@ -682,9 +874,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
if (something_nonoverriddable){
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
if (layerCount == 0) {
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
if (extruder_override == 0) {
layer_tools.extruders.emplace_back(layer_tools.wall_filament(region) + 1);
if (layerCount == 0) {
firstLayerExtruders.emplace_back(layer_tools.wall_filament(region) + 1);
}
} else {
const unsigned int resolved = resolve_mixed(extruder_override,
layerCount,
float(layer->print_z),
float(layer->height));
layer_tools.extruders.emplace_back(resolved);
if (layerCount == 0)
firstLayerExtruders.emplace_back(resolved);
}
}
@@ -710,13 +912,17 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
if (something_nonoverriddable || !m_print_config_ptr) {
if (extruder_override == 0) {
if (has_solid_infill)
layer_tools.extruders.emplace_back(region.config().solid_infill_filament);
if (has_infill)
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament);
} else if (has_solid_infill || has_infill)
layer_tools.extruders.emplace_back(extruder_override);
if (extruder_override == 0) {
if (has_solid_infill)
layer_tools.extruders.emplace_back(layer_tools.solid_infill_filament(region) + 1);
if (has_infill)
layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament(region) + 1);
} else if (has_solid_infill || has_infill) {
layer_tools.extruders.emplace_back(resolve_mixed(extruder_override,
layerCount,
float(layer->print_z),
float(layer->height)));
}
}
if (has_solid_infill || has_infill)
layer_tools.has_object = true;
@@ -1833,5 +2039,22 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
return -1;
}
// Resolve a 1-based filament ID through the mixed-filament manager.
unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based,
int layer_index,
float layer_print_z,
float layer_height) const
{
return resolve_mixed_with_layer_heights(m_mixed_mgr,
m_num_physical,
filament_id_1based,
layer_index,
layer_print_z,
layer_height,
m_mixed_layer_height_a,
m_mixed_layer_height_b,
m_mixed_base_layer_height);
}
} // namespace Slic3r

View File

@@ -4,6 +4,7 @@
#define slic3r_ToolOrdering_hpp_
#include "../libslic3r.h"
#include "../MixedFilament.hpp"
#include <utility>
@@ -145,6 +146,12 @@ public:
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion &region) const;
// Stub helpers for per-layer infill override (Task 14). Each returns
// the configured 1-based filament ID without per-layer logic until Task 14 lands.
unsigned int sparse_infill_filament_id_1based(const PrintRegion &region) const;
unsigned int infill_filament_id_1based(const PrintRegion &region) const;
bool use_base_infill_filament(const PrintRegion &region) const;
coordf_t print_z = 0.;
bool has_object = false;
bool has_support = false;
@@ -153,6 +160,27 @@ public:
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
// If not overriden, it is set to 0.
unsigned int extruder_override = 0;
// Mixed-filament resolution context (set by ToolOrdering during collect_extruders).
const MixedFilamentManager *mixed_mgr = nullptr;
size_t num_physical = 0;
// Sequential layer index (0-based), used by mixed-filament resolution.
int layer_index = -1;
// Total number of object layers for the current print object.
int object_layer_count = 0;
// Actual layer height for this print_z where available.
coordf_t layer_height = 0.;
// Optional mixed-layer cadence override from print settings.
float mixed_layer_height_a = 0.f;
float mixed_layer_height_b = 0.f;
float mixed_base_layer_height = 0.2f;
// Resolve a configured 1-based filament ID to a physical 1-based ID via the
// mixed-filament manager for this layer. Returns input unchanged if no manager
// is set or the ID is not a mixed slot.
bool is_mixed_slot_0based(unsigned int filament_id_0based) const {
return mixed_mgr && mixed_mgr->is_mixed(filament_id_0based + 1, num_physical);
}
// Should a skirt be printed at this layer?
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
bool has_skirt = false;
@@ -172,6 +200,10 @@ public:
return m_wiping_extrusions;
}
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
// Returns input unchanged when mixed_mgr is null or ID is not a mixed slot.
unsigned int resolve_mixed_1based(unsigned int filament_id_1based) const;
private:
// This object holds list of extrusion that will be used for extruder wiping
WipingExtrusions m_wiping_extrusions;
@@ -254,6 +286,14 @@ public:
bool has_non_support_filament(const PrintConfig &config);
// Resolve a 1-based filament ID through the mixed-filament manager.
// Returns the resolved physical extruder (1-based). If the ID is not a
// mixed filament or no manager is set, returns the input unchanged.
unsigned int resolve_mixed(unsigned int filament_id_1based,
int layer_index,
float layer_print_z = 0.f,
float layer_height = 0.f) const;
private:
void initialize_layers(std::vector<coordf_t> &zs);
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
@@ -262,6 +302,8 @@ private:
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height);
void collect_extruder_statistics(bool prime_multi_material);
void reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer);
// Read mixed_color_layer_height_a/b from config and cache in m_mixed_layer_height_*.
void update_mixed_layer_height_settings();
// BBS
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
@@ -278,6 +320,13 @@ private:
const PrintConfig* m_print_config_ptr = nullptr;
const PrintObject* m_print_object_ptr = nullptr;
Print* m_print;
// Mixed filament support: pointer to manager (owned by Print) and
// number of physical extruders.
const MixedFilamentManager* m_mixed_mgr = nullptr;
size_t m_num_physical = 0;
float m_mixed_layer_height_a = 0.f;
float m_mixed_layer_height_b = 0.f;
float m_mixed_base_layer_height = 0.2f;
bool m_sorted = false;
FilamentChangeStats m_stats_by_single_extruder;

View File

@@ -6,6 +6,7 @@
#include <vector>
#include <numeric>
#include <memory>
#include <limits>
#include <sstream>
#include <iomanip>
@@ -19,6 +20,7 @@
#include "Fill/FillRectilinear.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r
@@ -30,10 +32,16 @@ static const double wipe_tower_wall_infill_overlap = 0.0;
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001f / SCALING_FACTOR_INTERNAL;
static constexpr int arc_fit_size = 20;
static constexpr size_t LEGACY_NO_TOOL = static_cast<size_t>(std::numeric_limits<unsigned int>::max());
#define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR_INTERNAL)
enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow };
static const std::map<float, float> nozzle_diameter_to_nozzle_change_width{{0.2f, 0.5f}, {0.4f, 1.0f}, {0.6f, 1.2f}, {0.8f, 1.4f}};
inline bool is_no_tool_sentinel(size_t tool)
{
return tool == LEGACY_NO_TOOL || tool == size_t(-1);
}
inline float align_round(float value, float base) { return std::round(value / base) * base; }
inline float align_ceil(float value, float base) { return std::ceil(value / base) * base; }
@@ -1257,6 +1265,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_extra_flow(float(config.wipe_tower_extra_flow/100.)),
m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)),
m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing/100.)),
m_local_z_wipe_tower_purge_lines(float(config.local_z_wipe_tower_purge_lines)),
m_y_shift(0.f),
m_z_pos(0.f),
m_bridging(float(config.wipe_tower_bridging)),
@@ -1515,49 +1524,50 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
return results;
}
WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
WipeTower::ToolChangeResult WipeTower2::emit_planned_tool_change(const WipeTowerInfo::ToolChange *tool_change)
{
size_t old_tool = m_current_tool;
if (tool_change != nullptr && m_current_tool != tool_change->old_tool) {
BOOST_LOG_TRIVIAL(warning) << "Wipe tower tool state mismatch, realigning to planned toolchange"
<< " layer_z=" << (m_layer_info != m_plan.end() ? m_layer_info->z : -1.f)
<< " current_tool=" << m_current_tool
<< " planned_old_tool=" << tool_change->old_tool
<< " planned_new_tool=" << tool_change->new_tool;
m_current_tool = tool_change->old_tool;
}
float wipe_area = 0.f;
float wipe_volume = 0.f;
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
// Finds this toolchange info
if (tool != (unsigned int)(-1))
{
for (const auto &b : m_layer_info->tool_changes)
if ( b.new_tool == tool ) {
wipe_volume = b.wipe_volume;
wipe_area = b.required_depth;
break;
}
}
else {
// Otherwise we are going to Unload only. And m_layer_info would be invalid.
}
if (interface_layer && tool != (unsigned int)(-1) && tool < m_filpar.size()) {
const size_t tool = tool_change != nullptr ? tool_change->new_tool : LEGACY_NO_TOOL;
const size_t old_tool = m_current_tool;
float wipe_area = 0.f;
float wipe_volume = 0.f;
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
if (tool_change != nullptr) {
wipe_volume = tool_change->wipe_volume;
wipe_area = tool_change->required_depth;
}
if (interface_layer && !is_no_tool_sentinel(tool) && tool < m_filpar.size()) {
float extra_purge_length = m_filpar[tool].tower_interface_purge_length;
if (extra_purge_length > 0.f) {
wipe_volume += extra_purge_length * m_filpar[tool].filament_area;
}
}
WipeTower::box_coordinates cleaning_box(
Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
m_wipe_tower_width - m_perimeter_width,
(tool != (unsigned int)(-1) ? wipe_area+m_depth_traversed-0.5f*m_perimeter_width
: m_wipe_tower_depth-m_perimeter_width));
WipeTower::box_coordinates cleaning_box(Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width,
(!is_no_tool_sentinel(tool) ? wipe_area + m_depth_traversed - 0.5f * m_perimeter_width :
m_wipe_tower_depth - m_perimeter_width));
WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting);
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
.append(";--------------------\n"
"; CP TOOLCHANGE START\n");
WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting);
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.set_y_shift(m_y_shift + (!is_no_tool_sentinel(tool) && (m_current_shape == SHAPE_REVERSED) ?
m_layer_info->depth - m_layer_info->toolchanges_depth() :
0.f))
.append(";--------------------\n"
"; CP TOOLCHANGE START\n");
if (tool != (unsigned)(-1)){
if (!is_no_tool_sentinel(tool)) {
writer.comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based
writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str())
.append(";--------------------\n");
@@ -1574,8 +1584,10 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
if (m_set_extruder_trimpot)
writer.set_extruder_trimpot(750);
m_active_tool_change = tool_change;
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
if (tool != (unsigned int)-1){ // This is not the last change.
if (!is_no_tool_sentinel(tool)) { // This is not the last change.
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
@@ -1609,6 +1621,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
} else
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature);
m_active_tool_change = nullptr;
m_depth_traversed += wipe_area;
if (m_set_extruder_trimpot)
@@ -1625,7 +1638,46 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
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, false, interface_layer);
WipeTower::ToolChangeResult result = construct_tcr(writer, false, old_tool, false, interface_layer);
result.purge_volume = wipe_volume;
return result;
}
WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
{
const WipeTowerInfo::ToolChange *planned_tool_change = nullptr;
if (!is_no_tool_sentinel(tool)) {
for (const WipeTowerInfo::ToolChange &entry : m_layer_info->tool_changes) {
if (entry.old_tool == m_current_tool && entry.new_tool == tool) {
planned_tool_change = &entry;
break;
}
}
if (planned_tool_change == nullptr) {
for (const WipeTowerInfo::ToolChange &entry : m_layer_info->tool_changes) {
if (entry.new_tool == tool) {
planned_tool_change = &entry;
break;
}
}
}
if (planned_tool_change == nullptr) {
std::ostringstream planned_sequence;
for (size_t idx = 0; idx < m_layer_info->tool_changes.size(); ++idx) {
if (idx != 0)
planned_sequence << ",";
planned_sequence << m_layer_info->tool_changes[idx].old_tool << "->" << m_layer_info->tool_changes[idx].new_tool;
}
BOOST_LOG_TRIVIAL(error) << "Wipe tower toolchange not found in plan"
<< " layer_z=" << (m_layer_info != m_plan.end() ? m_layer_info->z : -1.f)
<< " current_tool=" << m_current_tool
<< " requested_new_tool=" << tool
<< " planned_sequence=[" << planned_sequence.str() << "]";
throw Slic3r::RuntimeError("Wipe tower toolchange not found in plan.");
}
}
return emit_planned_tool_change(planned_tool_change);
}
@@ -1678,22 +1730,15 @@ void WipeTower2::toolchange_Unload(
else
sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width;
float sum_of_depths = 0.f;
for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
if (tch.old_tool == m_current_tool) {
sum_of_depths += tch.ramming_depth;
float ramming_end_y = sum_of_depths;
ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line
if (m_active_tool_change != nullptr) {
float ramming_end_y = cumulative_toolchange_depth_before(m_active_tool_change) + m_active_tool_change->ramming_depth;
ramming_end_y -= (y_step / m_extra_spacing_ramming - m_perimeter_width) / 2.f; // center of final ramming line
if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
(m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
{
writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y());
remaining -= tch.first_wipe_line-1.f*m_perimeter_width;
}
break;
if ((m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f * m_perimeter_width) ||
(m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f * m_perimeter_width)) {
writer.extrude(xl + m_active_tool_change->first_wipe_line - 1.f * m_perimeter_width, writer.y());
remaining -= m_active_tool_change->first_wipe_line - 1.f * m_perimeter_width;
}
sum_of_depths += tch.required_depth;
}
}
@@ -1989,8 +2034,16 @@ void WipeTower2::toolchange_Wipe(
.add_wipe_point(writer.x(), writer.y() - dy)
.add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy);
if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool)
m_left_to_right = !m_left_to_right;
if (m_layer_info != m_plan.end()) {
size_t final_tool_on_layer = m_current_tool;
if (!m_layer_info->tool_changes.empty())
final_tool_on_layer = m_layer_info->tool_changes.back().new_tool;
else if (!m_layer_info->local_z_tool_changes.empty())
final_tool_on_layer = m_layer_info->local_z_tool_changes.back().new_tool;
if (m_current_tool != final_tool_on_layer)
m_left_to_right = !m_left_to_right;
}
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
writer.change_analyzer_line_width(m_perimeter_width);
@@ -2019,7 +2072,8 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f);
if (m_enable_tower_interface_features && m_prev_layer_had_interface)
feedrate = std::min(feedrate, 20.f * 60.f);
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
float reserve_depth = m_layer_info->local_z_reserve_depth();
float current_depth = std::max(0.f, m_layer_info->depth - m_layer_info->toolchanges_depth() - reserve_depth);
WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)),
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
@@ -2050,14 +2104,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
// Is there a soluble filament wiped/rammed at the next layer?
// If so, the infill should not be sparse.
bool solid_infill = m_layer_info+1 == m_plan.end()
? false
: std::any_of((m_layer_info+1)->tool_changes.begin(),
(m_layer_info+1)->tool_changes.end(),
[this](const WipeTowerInfo::ToolChange& tch) {
return m_filpar[tch.new_tool].is_soluble
|| m_filpar[tch.old_tool].is_soluble;
});
bool solid_infill = m_layer_info + 1 == m_plan.end() ? false : layer_has_soluble_toolchange(*(m_layer_info + 1));
solid_infill |= first_layer && m_adhesion;
if (solid_infill) {
@@ -2249,6 +2296,205 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
}
void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume)
{
assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON);
if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par)
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 || m_plan.size() == 1))
m_first_layer_idx = m_plan.size() - 1;
if (old_tool == new_tool)
return;
float width = m_wipe_tower_width - 3 * m_perimeter_width;
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(),
m_filpar[old_tool].ramming_speed.end(), 0.f),
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, layer_height_par);
float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) *
(m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator *
m_filpar[old_tool].ramming_step_multiplicator) *
m_extra_spacing_ramming) :
0;
float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width);
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow,
m_extra_spacing_wipe, width);
m_plan.back().local_z_tool_changes.push_back(
WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
}
void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_t reserve_slot_count, float wipe_volume)
{
if (reserve_slot_count == 0)
return;
assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON);
if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par)
m_plan.push_back(WipeTowerInfo(z_par, layer_height_par));
const float mini_wipe_depth = m_local_z_wipe_tower_purge_lines * m_perimeter_width * m_extra_spacing_wipe;
const float wipe_width = std::max(0.f, m_wipe_tower_width - 3.f * m_perimeter_width);
const float wiping_depth = wipe_width > WT_EPSILON ?
get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, m_perimeter_width, m_extra_flow,
m_extra_spacing_wipe, wipe_width) :
0.f;
float max_ramming_depth = 0.f;
if (wipe_width > WT_EPSILON) {
for (const FilamentParameters& filament : m_filpar) {
const bool do_ramming = (m_semm && m_enable_filament_ramming) || filament.multitool_ramming;
if (!do_ramming || filament.ramming_speed.empty())
continue;
const float line_width = m_perimeter_width * filament.ramming_line_width_multiplicator;
const float line_step =
(m_perimeter_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) *
m_extra_spacing_ramming;
if (line_width <= WT_EPSILON || line_step <= WT_EPSILON)
continue;
const float ramming_volume = 0.25f * std::accumulate(filament.ramming_speed.begin(), filament.ramming_speed.end(), 0.f);
const float length_to_extrude = volume_to_length(ramming_volume, line_width, layer_height_par);
const float ramming_depth =
(float(int(length_to_extrude / wipe_width) + 1) * line_step);
max_ramming_depth = std::max(max_ramming_depth, ramming_depth);
}
}
const float full_toolchange_depth = max_ramming_depth + wiping_depth;
const float slot_depth =
std::max(2.5f * m_perimeter_width, std::max(mini_wipe_depth + m_perimeter_width, full_toolchange_depth + m_perimeter_width));
WipeTowerInfo &layer = m_plan.back();
layer.local_z_reserve_slot_depth = std::max(layer.local_z_reserve_slot_depth, slot_depth);
layer.local_z_reserve_slot_count += reserve_slot_count;
}
WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool,
const WipeTower::box_coordinates& cleaning_box,
float wipe_volume)
{
const size_t old_tool = m_current_tool;
WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting);
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.append(";--------------------\n"
"; CP TOOLCHANGE START\n");
writer.comment_with_value(" toolchange #", m_num_tool_changes + 1);
writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " +
m_filpar[new_tool].material + "\n")
.c_str())
.append(";--------------------\n");
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n");
writer.speed_override_backup();
writer.speed_override(100);
writer.set_initial_position(cleaning_box.ld, m_wipe_tower_width, m_wipe_tower_depth, 0.f);
if (m_set_extruder_trimpot)
writer.set_extruder_trimpot(750);
const int old_tool_temp = is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature;
const int new_tool_temp = is_first_layer() ? m_filpar[new_tool].first_layer_temperature : m_filpar[new_tool].temperature;
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, old_tool_temp, new_tool_temp);
toolchange_Change(writer, new_tool, m_filpar[new_tool].material);
toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(), writer.y() - m_perimeter_width);
toolchange_Wipe(writer, cleaning_box, wipe_volume, false);
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n");
++m_num_tool_changes;
if (m_set_extruder_trimpot)
writer.set_extruder_trimpot(550);
writer.speed_override_restore();
writer.feedrate(m_travel_speed * 60.f)
.flush_planner_queue()
.reset_extruder()
.append("; CP TOOLCHANGE END\n"
";------------------\n"
"\n\n");
if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
WipeTower::ToolChangeResult result = construct_tcr(writer, false, old_tool, false);
result.purge_volume = wipe_volume;
return result;
}
namespace {
// Helper: rotate a point within the wipe tower local coordinate system
// by internal_angle_deg, shifting by y_shift.
static Vec2f rotate_local_z_reserve_point(const Vec2f& pt, float tower_width, float tower_depth,
float y_shift, float internal_angle_deg)
{
Vec2f shifted = pt;
shifted.x() -= tower_width / 2.f;
shifted.y() += y_shift - tower_depth / 2.f;
const double angle = internal_angle_deg * double(M_PI / 180.);
const double c = std::cos(angle);
const double s = std::sin(angle);
return Vec2f(float(shifted.x() * c - shifted.y() * s) + tower_width / 2.f,
float(shifted.x() * s + shifted.y() * c) + tower_depth / 2.f);
}
} // namespace
std::vector<std::vector<WipeTower::box_coordinates>> WipeTower2::get_local_z_reserve_boxes() const
{
std::vector<std::vector<WipeTower::box_coordinates>> out;
out.reserve(m_plan.size());
for (size_t layer_idx = 0; layer_idx < m_plan.size(); ++layer_idx) {
const WipeTowerInfo& layer = m_plan[layer_idx];
std::vector<WipeTower::box_coordinates> layer_boxes;
layer_boxes.reserve(layer.local_z_reserve_slot_count);
if (layer.local_z_reserve_slot_count > 0 && layer.local_z_reserve_slot_depth > WT_EPSILON) {
const float y_shift = layer.depth < m_wipe_tower_depth - m_perimeter_width ?
(m_wipe_tower_depth - layer.depth - m_perimeter_width) / 2.f : 0.f;
const float internal_angle = (layer_idx % 2 == 0) ? 180.f : 0.f;
for (size_t slot_idx = 0; slot_idx < layer.local_z_reserve_slot_count; ++slot_idx) {
const float slot_start = layer.toolchanges_depth() + float(slot_idx) * layer.local_z_reserve_slot_depth;
const float width = std::max(0.f, m_wipe_tower_width - 2.f * m_perimeter_width);
const float height = std::max(0.f, layer.local_z_reserve_slot_depth - m_perimeter_width);
if (width <= WT_EPSILON || height <= WT_EPSILON)
continue;
const Vec2f ld_unrotated(m_perimeter_width, slot_start + m_perimeter_width);
const Vec2f rd_unrotated = ld_unrotated + Vec2f(width, 0.f);
const Vec2f ru_unrotated = ld_unrotated + Vec2f(width, height);
const Vec2f lu_unrotated = ld_unrotated + Vec2f(0.f, height);
const Vec2f ld = rotate_local_z_reserve_point(ld_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f rd = rotate_local_z_reserve_point(rd_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f ru = rotate_local_z_reserve_point(ru_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f lu = rotate_local_z_reserve_point(lu_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const float min_x = std::min({ld.x(), rd.x(), ru.x(), lu.x()});
const float max_x = std::max({ld.x(), rd.x(), ru.x(), lu.x()});
const float min_y = std::min({ld.y(), rd.y(), ru.y(), lu.y()});
const float max_y = std::max({ld.y(), rd.y(), ru.y(), lu.y()});
layer_boxes.emplace_back(Vec2f(min_x, min_y), max_x - min_x, max_y - min_y);
}
}
out.emplace_back(std::move(layer_boxes));
}
return out;
}
void WipeTower2::plan_tower()
@@ -2262,7 +2508,7 @@ void WipeTower2::plan_tower()
for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index)
{
float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth());
float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].planned_depth());
m_plan[layer_index].depth = this_layer_depth;
if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
@@ -2293,7 +2539,7 @@ void WipeTower2::save_on_last_wipe()
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
auto& toolchange = m_layer_info->tool_changes[i];
tool_change(toolchange.new_tool);
emit_planned_tool_change(&toolchange);
if (i == idx) {
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
@@ -2339,8 +2585,38 @@ int WipeTower2::first_toolchange_to_nonsoluble(
return tool_changes.empty() ? -1 : 0;
}
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
WipeTower::ToolChangeResult& second)
bool WipeTower2::layer_has_soluble_toolchange(const WipeTowerInfo &layer) const
{
auto has_soluble = [this](const std::vector<WipeTowerInfo::ToolChange> &tool_changes) {
return std::any_of(tool_changes.begin(), tool_changes.end(), [this](const WipeTowerInfo::ToolChange &toolchange) {
return m_filpar[toolchange.new_tool].is_soluble || m_filpar[toolchange.old_tool].is_soluble;
});
};
return has_soluble(layer.local_z_tool_changes) || has_soluble(layer.tool_changes);
}
float WipeTower2::cumulative_toolchange_depth_before(const WipeTowerInfo::ToolChange *tool_change) const
{
if (tool_change == nullptr || m_layer_info == m_plan.end())
return 0.f;
float depth = 0.f;
for (const WipeTowerInfo::ToolChange &entry : m_layer_info->local_z_tool_changes) {
if (&entry == tool_change)
return depth;
depth += entry.required_depth;
}
for (const WipeTowerInfo::ToolChange &entry : m_layer_info->tool_changes) {
if (&entry == tool_change)
return depth;
depth += entry.required_depth;
}
return depth;
}
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, WipeTower::ToolChangeResult& second)
{
assert(first.new_tool == second.initial_tool);
WipeTower::ToolChangeResult out = first;
@@ -2358,10 +2634,11 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
return out;
}
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
// Resulting ToolChangeResults are appended into vector "result"
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower.
// Normal per-layer toolchanges are appended into "result", while Local-Z phase-b toolchanges are
// emitted into "local_z_result" so G-code can consume them before the nominal layer loop.
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>& result,
std::vector<std::vector<WipeTower::ToolChangeResult>>& local_z_result)
{
if (m_plan.empty())
return;
@@ -2383,8 +2660,12 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
m_layer_info = m_plan.begin();
m_current_height = 0.f;
// we don't know which extruder to start with - we'll set it according to the first toolchange
// We don't know which extruder to start with, so take the first actual toolchange on the tower.
for (const auto& layer : m_plan) {
if (!layer.local_z_tool_changes.empty()) {
m_current_tool = layer.local_z_tool_changes.front().old_tool;
break;
}
if (!layer.tool_changes.empty()) {
m_current_tool = layer.tool_changes.front().old_tool;
break;
@@ -2400,7 +2681,15 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
for (const WipeTower2::WipeTowerInfo& layer : m_plan)
{
std::vector<WipeTower::ToolChangeResult> layer_result;
set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
std::vector<WipeTower::ToolChangeResult> local_z_layer_result;
BOOST_LOG_TRIVIAL(debug) << "Wipe tower layer plan"
<< " z=" << layer.z
<< " height=" << layer.height
<< " nominal_toolchanges=" << layer.tool_changes.size()
<< " local_z_toolchanges=" << layer.local_z_tool_changes.size()
<< " reserve_slots=" << layer.local_z_reserve_slot_count
<< " planned_depth=" << layer.planned_depth();
set_layer(layer.z, layer.height, 0, false /*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
m_internal_rotation += 180.f;
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
@@ -2409,15 +2698,18 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
WipeTower::ToolChangeResult finish_layer_tcr;
for (const WipeTowerInfo::ToolChange &toolchange : layer.local_z_tool_changes)
local_z_layer_result.emplace_back(emit_planned_tool_change(&toolchange));
if (idx == -1) {
// if there is no toolchange switching to non-soluble, finish layer
// will be called at the very beginning. That's the last possibility
// where a nonsoluble tool can be.
// If there is no nominal-layer toolchange switching to non-soluble,
// finish_layer still runs after any Local-Z toolchanges already planned
// onto this tower layer.
finish_layer_tcr = finish_layer();
}
for (int i=0; i<int(layer.tool_changes.size()); ++i) {
layer_result.emplace_back(tool_change(layer.tool_changes[i].new_tool));
for (int i = 0; i < int(layer.tool_changes.size()); ++i) {
layer_result.emplace_back(emit_planned_tool_change(&layer.tool_changes[i]));
if (i == idx) // finish_layer will be called after this toolchange
finish_layer_tcr = finish_layer();
}
@@ -2435,7 +2727,8 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
}
result.emplace_back(std::move(layer_result));
result.emplace_back(std::move(layer_result));
local_z_result.emplace_back(std::move(local_z_layer_result));
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
m_used_filament_length_until_layer.emplace_back();

View File

@@ -51,12 +51,17 @@ public:
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f);
void plan_local_z_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f);
// Reserve Local-Z wipe-tower slots for unplanned toolchanges during Local-Z sub-layer emission.
void plan_local_z_reserve(float z_par, float layer_height_par, size_t reserve_slot_count, float wipe_volume = 0.f);
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result,
std::vector<std::vector<WipeTower::ToolChangeResult>> &local_z_result);
float get_depth() const { return m_wipe_tower_depth; }
std::vector<std::pair<float, float>> get_z_and_depth_pairs() const;
std::vector<std::vector<WipeTower::box_coordinates>> get_local_z_reserve_boxes() const;
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
float get_wipe_tower_height() const { return m_wipe_tower_height; }
// ORCA: Match WipeTower API used by Print skirt/brim planning.
@@ -132,6 +137,9 @@ public:
// Returns gcode for a toolchange and a final print head position.
// On the first layer, extrude a brim around the future wipe tower first.
WipeTower::ToolChangeResult tool_change(size_t new_tool);
// Emit a mini toolchange into a pre-reserved Local-Z wipe slot (does not consume m_plan).
WipeTower::ToolChangeResult local_z_tool_change(size_t new_tool, const WipeTower::box_coordinates& cleaning_box, float wipe_volume);
void set_current_tool(size_t tool) { m_current_tool = tool; }
// Fill the unfilled space with a sparse infill.
// Call this method only if layer_finished() is false.
@@ -182,6 +190,8 @@ public:
};
private:
struct WipeTowerInfo;
enum wipe_shape // A fill-in direction
{
SHAPE_NORMAL = 1,
@@ -260,6 +270,9 @@ private:
// Extruder specific parameters.
std::vector<FilamentParameters> m_filpar;
// Number of wipe-tower purge lines to reserve per Local-Z unplanned toolchange slot.
float m_local_z_wipe_tower_purge_lines = 3.f;
// State of the wipe tower generator.
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
@@ -309,9 +322,16 @@ private:
float z; // z position of the layer
float height; // layer height
float depth; // depth of the layer based on all layers above
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
float normal_toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
float local_z_toolchanges_depth() const { float sum = 0.f; for (const auto &a : local_z_tool_changes) sum += a.required_depth; return sum; }
float toolchanges_depth() const { return normal_toolchanges_depth() + local_z_toolchanges_depth(); }
float local_z_reserve_slot_depth { 0.f };
size_t local_z_reserve_slot_count { 0 };
float local_z_reserve_depth() const { return local_z_reserve_slot_depth * float(local_z_reserve_slot_count); }
float planned_depth() const { return toolchanges_depth() + local_z_reserve_depth(); }
std::vector<ToolChange> tool_changes;
std::vector<ToolChange> local_z_tool_changes;
WipeTowerInfo(float z_par, float layer_height_par)
: z{z_par}, height{layer_height_par}, depth{0} {}
@@ -319,6 +339,7 @@ private:
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
const WipeTowerInfo::ToolChange *m_active_tool_change = nullptr;
// This sums height of all extruded layers, not counting the layers which
// will be later removed when the "no_sparse_layers" is used.
@@ -332,6 +353,9 @@ private:
// ot -1 if there is no such toolchange.
int first_toolchange_to_nonsoluble(
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
bool layer_has_soluble_toolchange(const WipeTowerInfo &layer) const;
float cumulative_toolchange_depth_before(const WipeTowerInfo::ToolChange *tool_change) const;
WipeTower::ToolChangeResult emit_planned_tool_change(const WipeTowerInfo::ToolChange *tool_change);
void toolchange_Unload(
WipeTowerWriter2 &writer,