mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Extract and Unify Wipe Tower Estimation
This commit is contained in:
@@ -272,6 +272,8 @@ set(lisbslic3r_sources
|
||||
GCode/WipeTower2.hpp
|
||||
GCode/WipeTower.cpp
|
||||
GCode/WipeTower.hpp
|
||||
GCode/WipeTowerEstimate.cpp
|
||||
GCode/WipeTowerEstimate.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry/ArcWelder.hpp
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "WipeTowerEstimate.hpp"
|
||||
|
||||
#include "WipeTower.hpp"
|
||||
#include "WipeTower2.hpp"
|
||||
#include "../Config.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, size_t filaments_cnt, double layer_height, double max_object_height, bool any_raft)
|
||||
{
|
||||
WipeTowerFootprint footprint;
|
||||
footprint.height = max_object_height;
|
||||
if (filaments_cnt == 0 || layer_height < EPSILON)
|
||||
return footprint;
|
||||
|
||||
// Every caller today declares all these keys, but the signature accepts any ConfigBase:
|
||||
// fall back to the key's declared default, never to a hand-copied constant.
|
||||
auto option_of = [&config](const char *key) -> const ConfigOption * {
|
||||
if (const ConfigOption *opt = config.option(key); opt != nullptr)
|
||||
return opt;
|
||||
if (const ConfigDef *def = config.def(); def != nullptr)
|
||||
if (const ConfigOptionDef *opt_def = def->get(key); opt_def != nullptr)
|
||||
return opt_def->default_value.get();
|
||||
return nullptr;
|
||||
};
|
||||
auto opt_float = [&option_of](const char *key) {
|
||||
const ConfigOption *opt = option_of(key);
|
||||
return opt != nullptr ? opt->getFloat() : 0.;
|
||||
};
|
||||
auto opt_bool = [&option_of](const char *key) {
|
||||
const ConfigOption *opt = option_of(key);
|
||||
return opt != nullptr && opt->getBool();
|
||||
};
|
||||
// By value, not by concrete type: a static PrintConfig holds ConfigOptionEnum<T>, a
|
||||
// DynamicConfig built from presets holds ConfigOptionEnumGeneric, and both answer getInt().
|
||||
auto opt_enum = [&option_of](const char *key, int fallback) {
|
||||
const ConfigOption *opt = option_of(key);
|
||||
return opt != nullptr ? opt->getInt() : fallback;
|
||||
};
|
||||
auto max_of = [&option_of](const char *key, double fallback) {
|
||||
const auto *opt = dynamic_cast<const ConfigOptionFloats *>(option_of(key));
|
||||
return (opt != nullptr && !opt->values.empty()) ? *std::max_element(opt->values.begin(), opt->values.end()) : fallback;
|
||||
};
|
||||
|
||||
const double width = opt_float("prime_tower_width");
|
||||
const double prime_volume = opt_float("prime_volume");
|
||||
const double extra_spacing = opt_float("prime_tower_infill_gap") / 100.;
|
||||
double rib_width = opt_float("wipe_tower_rib_width");
|
||||
const double extra_rib_length = opt_float("wipe_tower_extra_rib_length");
|
||||
const auto *nozzle_opt = dynamic_cast<const ConfigOptionFloats *>(option_of("nozzle_diameter"));
|
||||
const bool dual_nozzle = nozzle_opt != nullptr && nozzle_opt->values.size() == 2;
|
||||
const bool rib_wall = opt_enum("wipe_tower_wall_type", int(WipeTowerWallType::wtwRectangle)) == int(WipeTowerWallType::wtwRib);
|
||||
const bool smooth_timelapse = opt_enum("timelapse_type", int(TimelapseType::tlTraditional)) == int(TimelapseType::tlSmooth);
|
||||
// Reasons a tower is printed with no tool change to purge for.
|
||||
const bool need_wipe_tower = smooth_timelapse || opt_bool("enable_wrapping_detection") || any_raft;
|
||||
|
||||
// No tool change, nothing to purge; smooth timelapse still primes once.
|
||||
size_t purge_count = 0;
|
||||
if (filaments_cnt > 1)
|
||||
purge_count = dual_nozzle ? filaments_cnt : filaments_cnt - 1;
|
||||
else if (smooth_timelapse)
|
||||
purge_count = 1;
|
||||
|
||||
double volume = prime_volume * double(purge_count);
|
||||
if (dual_nozzle) {
|
||||
// Dual-nozzle printers also purge the filament change length on the tower.
|
||||
const double length = max_of("filament_change_length", 0.);
|
||||
const double diameter = max_of("filament_diameter", 1.75);
|
||||
volume += length * PI * diameter * diameter / 4. * double(filaments_cnt / 2);
|
||||
}
|
||||
// Single-extruder multi-material purges the flush matrix instead of the prime volume.
|
||||
const bool semm_flush = opt_bool("purge_in_prime_tower") && opt_bool("single_extruder_multi_material");
|
||||
if (semm_flush)
|
||||
volume = WipeTower2::estimate_semm_flush_volume(config, filaments_cnt);
|
||||
|
||||
// Both wall types decide this together: over-reserving only wastes bed area, but
|
||||
// reporting no tower for one that is built collapses the validation hull to a point.
|
||||
if (volume < EPSILON && !need_wipe_tower)
|
||||
return footprint;
|
||||
|
||||
const double min_depth = WipeTower::get_limit_depth_by_height(float(max_object_height));
|
||||
if (rib_wall) {
|
||||
// A rib wall squares the tower; the ribs run the diagonal and bulge past the body.
|
||||
const double volume_depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||
double depth = std::max(min_depth, volume_depth);
|
||||
rib_width = std::min(rib_width, depth / 2.);
|
||||
depth = rib_width / std::sqrt(2.) + std::max(depth + extra_rib_length, volume_depth);
|
||||
footprint.width = footprint.depth = depth;
|
||||
} else {
|
||||
double depth = volume / (layer_height * width);
|
||||
// The flush volumes already hold the spacing between wipes.
|
||||
if (!semm_flush)
|
||||
depth *= extra_spacing;
|
||||
footprint.width = width;
|
||||
footprint.depth = std::max(min_depth, depth);
|
||||
}
|
||||
|
||||
footprint.brim_width = opt_float("prime_tower_brim_width");
|
||||
if (footprint.brim_width < 0)
|
||||
footprint.brim_width = WipeTower::get_auto_brim_by_height(float(max_object_height));
|
||||
return footprint;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ConfigBase;
|
||||
|
||||
// Pre-slice footprint of the wipe tower, shared by validation (Print), the GUI's placement
|
||||
// clamp/preview/arrange and the CLI placement. The arithmetic is shared; the inputs below are
|
||||
// not, so a change to how one caller derives them has to be mirrored in the others.
|
||||
struct WipeTowerFootprint
|
||||
{
|
||||
double width = 0.; // effective width: equals depth for a rib wall, which squares the tower
|
||||
double depth = 0.; // 0 when these inputs imply no tower
|
||||
double height = 0.; // tallest object; drives the stability floor and the auto brim
|
||||
double brim_width = 0.; // configured width, auto (-1) resolved by height
|
||||
};
|
||||
|
||||
// filaments_cnt: filaments purged on the plate. The config cannot see custom G-code tool
|
||||
// changes, so a count derived from the model must include them
|
||||
// (Print::extruders(true)) or a real tower is sized as if it were never built.
|
||||
// layer_height: thinnest layer the tower will be planned at.
|
||||
// any_raft: any object on the plate prints a raft, which puts the tower on every layer
|
||||
// below it. Caller-resolved: raft_layers is a PrintObjectConfig key, absent
|
||||
// from Print's config and overridable per object.
|
||||
WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, size_t filaments_cnt, double layer_height, double max_object_height, bool any_raft);
|
||||
|
||||
} // namespace Slic3r
|
||||
+27
-73
@@ -20,6 +20,7 @@
|
||||
#include "GCode.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "GCode/WipeTowerEstimate.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MaterialType.hpp"
|
||||
@@ -1031,20 +1032,20 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
|
||||
//BBS: add the wipe tower check logic
|
||||
const PrintConfig & config = print.config();
|
||||
int filaments_count = print.extruders().size();
|
||||
// Custom G-code tool changes (MultiAsSingle) build a real tower on a plate whose objects
|
||||
// all use one filament, so they have to be counted or the hull below collapses to a point.
|
||||
int filaments_count = print.extruders(true).size();
|
||||
int plate_index = print.get_plate_index();
|
||||
const Vec3d plate_origin = print.get_plate_origin();
|
||||
float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0);
|
||||
float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1);
|
||||
float width = config.prime_tower_width.value;
|
||||
float a = config.wipe_tower_rotation_angle.value;
|
||||
//float v = config.wiping_volume.value;
|
||||
|
||||
float depth = print.wipe_tower_data(filaments_count).depth;
|
||||
//float brim_width = print.wipe_tower_data(filaments_count).brim_width;
|
||||
|
||||
if (config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib)
|
||||
width = depth;
|
||||
// The estimate resolves the effective width (a rib wall squares the tower).
|
||||
const WipeTowerData &wipe_tower_estimate = print.wipe_tower_data(filaments_count);
|
||||
float width = wipe_tower_estimate.width;
|
||||
float depth = wipe_tower_estimate.depth;
|
||||
|
||||
Polygons convex_hulls_temp;
|
||||
if (print.has_wipe_tower()) {
|
||||
@@ -3997,74 +3998,27 @@ bool Print::has_wipe_tower() const
|
||||
|
||||
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
||||
{
|
||||
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
|
||||
double max_height = 0;
|
||||
for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) {
|
||||
double object_z = (double) m_objects[obj_idx]->size().z();
|
||||
max_height = std::max(unscale_(object_z), max_height);
|
||||
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
||||
// validation cannot reject a position the clamp just accepted.
|
||||
if (is_step_done(psWipeTower) || filaments_cnt == 0)
|
||||
return m_wipe_tower_data;
|
||||
|
||||
double max_height = 0.;
|
||||
double layer_height = std::numeric_limits<double>::max();
|
||||
bool any_raft = false;
|
||||
for (const PrintObject *object : m_objects) {
|
||||
max_height = std::max(max_height, unscale_(double(object->size().z())));
|
||||
layer_height = std::min(layer_height, object->config().layer_height.value);
|
||||
any_raft = any_raft || object->config().raft_layers.value > 0;
|
||||
}
|
||||
if (max_height < EPSILON) return m_wipe_tower_data;
|
||||
if (max_height < EPSILON)
|
||||
return m_wipe_tower_data;
|
||||
|
||||
double layer_height = 0.08f; // hard code layer height
|
||||
layer_height = m_objects.front()->config().layer_height.value;
|
||||
|
||||
auto timelapse_type = config().option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
||||
bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib);
|
||||
double extra_spacing = config().option("prime_tower_infill_gap")->getFloat() / 100.;
|
||||
double rib_width = config().option("wipe_tower_rib_width")->getFloat();
|
||||
|
||||
double filament_change_volume = 0.;
|
||||
{
|
||||
std::vector<double> filament_change_lengths;
|
||||
auto filament_change_lengths_opt = config().option<ConfigOptionFloats>("filament_change_length");
|
||||
if (filament_change_lengths_opt) filament_change_lengths = filament_change_lengths_opt->values;
|
||||
double length = filament_change_lengths.empty() ? 0 : *std::max_element(filament_change_lengths.begin(), filament_change_lengths.end());
|
||||
double diameter = 1.75;
|
||||
std::vector<double> diameters;
|
||||
auto filament_diameter_opt = config().option<ConfigOptionFloats>("filament_diameter");
|
||||
if (filament_diameter_opt) diameters = filament_diameter_opt->values;
|
||||
diameter = diameters.empty() ? diameter : *std::max_element(diameters.begin(), diameters.end());
|
||||
filament_change_volume = length * PI * diameter * diameter / 4.;
|
||||
}
|
||||
|
||||
|
||||
if (! is_step_done(psWipeTower) && filaments_cnt !=0) {
|
||||
double wipe_volume = m_config.prime_volume;
|
||||
int filament_depth_count = m_config.nozzle_diameter.values.size() == 2 ? filaments_cnt : filaments_cnt - 1;
|
||||
if (filaments_cnt == 1 && enable_timelapse_print()) filament_depth_count = 1;
|
||||
double volume = wipe_volume * filament_depth_count;
|
||||
if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2);
|
||||
|
||||
// Sizing should take into account currently set wiping volumes.
|
||||
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
|
||||
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
|
||||
const bool semm_flush = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
|
||||
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(m_config, filaments_cnt);
|
||||
|
||||
if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) {
|
||||
double depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||
if (need_wipe_tower || filaments_cnt > 1) {
|
||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
||||
depth += rib_width / std::sqrt(2) + config().wipe_tower_extra_rib_length.value;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||
}
|
||||
}
|
||||
else {
|
||||
double width = m_config.prime_tower_width;
|
||||
double depth = volume / (layer_height * width);
|
||||
// The flush volumes already hold the spacing between wipes.
|
||||
if (!semm_flush) depth *= extra_spacing;
|
||||
if (need_wipe_tower || depth > EPSILON) {
|
||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
||||
}
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||
}
|
||||
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
|
||||
}
|
||||
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(m_config, filaments_cnt, layer_height, max_height, any_raft);
|
||||
WipeTowerData &data = const_cast<Print *>(this)->m_wipe_tower_data;
|
||||
data.depth = float(footprint.depth);
|
||||
data.width = float(footprint.width);
|
||||
data.brim_width = float(footprint.brim_width);
|
||||
return m_wipe_tower_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -782,6 +782,9 @@ struct WipeTowerData
|
||||
|
||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||
float depth;
|
||||
// Effective width (a rib wall squares the tower). Pre-generation estimate only; once the
|
||||
// tower exists its mesh is exact.
|
||||
float width;
|
||||
std::vector<std::pair<float, float>> z_and_depth_pairs;
|
||||
float brim_width;
|
||||
float height;
|
||||
@@ -795,6 +798,7 @@ struct WipeTowerData
|
||||
used_filament.clear();
|
||||
number_of_toolchanges = -1;
|
||||
depth = 0.f;
|
||||
width = 0.f;
|
||||
brim_width = 0.f;
|
||||
height = 0.f;
|
||||
rib_offset = Vec2f::Zero();
|
||||
|
||||
Reference in New Issue
Block a user