mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 19:17:33 +00:00
Extract and Unify Wipe Tower Estimation
This commit is contained in:
@@ -4015,7 +4015,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse, new_extruder_count](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
|
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
|
||||||
plate_obj_size_info.obj_bbox= plate->get_objects_bounding_box();
|
plate_obj_size_info.obj_bbox= plate->get_objects_bounding_box();
|
||||||
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%, object bbox: min {%2%, %3%, %4%} - max {%5%, %6%, %7%}")
|
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%, object bbox: min {%2%, %3%, %4%} - max {%5%, %6%, %7%}")
|
||||||
%(plate_index+1) %plate_obj_size_info.obj_bbox.min.x() % plate_obj_size_info.obj_bbox.min.y() % plate_obj_size_info.obj_bbox.min.z() %plate_obj_size_info.obj_bbox.max.x() % plate_obj_size_info.obj_bbox.max.y() % plate_obj_size_info.obj_bbox.max.z();
|
%(plate_index+1) %plate_obj_size_info.obj_bbox.min.x() % plate_obj_size_info.obj_bbox.min.y() % plate_obj_size_info.obj_bbox.min.z() %plate_obj_size_info.obj_bbox.max.x() % plate_obj_size_info.obj_bbox.max.y() % plate_obj_size_info.obj_bbox.max.z();
|
||||||
@@ -4059,22 +4059,13 @@ int CLI::run(int argc, char **argv)
|
|||||||
plate_obj_size_info.wipe_x = wipe_x_option->get_at(plate_index);
|
plate_obj_size_info.wipe_x = wipe_x_option->get_at(plate_index);
|
||||||
plate_obj_size_info.wipe_y = wipe_y_option->get_at(plate_index);
|
plate_obj_size_info.wipe_y = wipe_y_option->get_at(plate_index);
|
||||||
|
|
||||||
ConfigOptionFloat* width_option = print_config.option<ConfigOptionFloat>("prime_tower_width", true);
|
// Body and brim from one estimate: resolving an auto (-1) brim against a different
|
||||||
plate_obj_size_info.wipe_width = width_option->value;
|
// height would size the two halves of the same tower from two different objects.
|
||||||
|
const WipeTowerFootprint footprint = plate->estimate_wipe_tower_footprint(print_config, filaments_cnt);
|
||||||
|
float brim_width = float(footprint.brim_width);
|
||||||
|
|
||||||
ConfigOptionFloat* brim_width_option = print_config.option<ConfigOptionFloat>("prime_tower_brim_width", true);
|
plate_obj_size_info.wipe_width = footprint.width;
|
||||||
float brim_width = brim_width_option->value;
|
plate_obj_size_info.wipe_depth = footprint.depth;
|
||||||
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float)plate_obj_size_info.obj_bbox.max.z());
|
|
||||||
|
|
||||||
ConfigOptionFloat* volume_option = print_config.option<ConfigOptionFloat>("prime_volume", true);
|
|
||||||
float wipe_volume = volume_option->value;
|
|
||||||
|
|
||||||
const ConfigOptionBool * wrapping_detection = print_config.option<ConfigOptionBool>("enable_wrapping_detection");
|
|
||||||
bool enable_wrapping = (wrapping_detection != nullptr) && wrapping_detection->value;
|
|
||||||
|
|
||||||
Vec3d wipe_tower_size = plate->estimate_wipe_tower_size(print_config, plate_obj_size_info.wipe_width, wipe_volume, new_extruder_count, filaments_cnt, false, enable_wrapping);
|
|
||||||
plate_obj_size_info.wipe_width = wipe_tower_size(0);
|
|
||||||
plate_obj_size_info.wipe_depth = wipe_tower_size(1);
|
|
||||||
|
|
||||||
Vec3d origin = plate->get_origin();
|
Vec3d origin = plate->get_origin();
|
||||||
Vec3d start(origin(0) + plate_obj_size_info.wipe_x - brim_width, origin(1) + plate_obj_size_info.wipe_y, 0.f);
|
Vec3d start(origin(0) + plate_obj_size_info.wipe_x - brim_width, origin(1) + plate_obj_size_info.wipe_y, 0.f);
|
||||||
@@ -4875,7 +4866,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
wipe_y_option->set_at(&wt_y_opt, i, 0);
|
wipe_y_option->set_at(&wt_y_opt, i, 0);
|
||||||
|
|
||||||
Vec3d wipe_tower_size, wipe_tower_pos;
|
Vec3d wipe_tower_size, wipe_tower_pos;
|
||||||
ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, i, wipe_tower_pos, wipe_tower_size, new_extruder_count, assemble_plate.filaments_count, true);
|
ArrangePolygon wipe_tower_ap = cur_plate->estimate_wipe_tower_polygon(m_print_config, i, wipe_tower_pos, wipe_tower_size, assemble_plate.filaments_count, true);
|
||||||
|
|
||||||
//update the new wp position
|
//update the new wp position
|
||||||
wt_x_opt.value = wipe_tower_pos(0);
|
wt_x_opt.value = wipe_tower_pos(0);
|
||||||
@@ -5175,7 +5166,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vec3d wipe_tower_size, wipe_tower_pos;
|
Vec3d wipe_tower_size, wipe_tower_pos;
|
||||||
ArrangePolygon wipe_tower_ap = partplate_list.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(m_print_config, plate_index_valid, wipe_tower_pos, wipe_tower_size, new_extruder_count, extruder_size, true);
|
ArrangePolygon wipe_tower_ap = partplate_list.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(m_print_config, plate_index_valid, wipe_tower_pos, wipe_tower_size, extruder_size, true);
|
||||||
|
|
||||||
//update the new wp position
|
//update the new wp position
|
||||||
if (bedid < plate_count) {
|
if (bedid < plate_count) {
|
||||||
@@ -5276,22 +5267,16 @@ int CLI::run(int argc, char **argv)
|
|||||||
|
|
||||||
//float depth = v * (filaments_cnt - 1) / (layer_height * w);
|
//float depth = v * (filaments_cnt - 1) / (layer_height * w);
|
||||||
|
|
||||||
const ConfigOptionBool *wrapping_detection = m_print_config.option<ConfigOptionBool>("enable_wrapping_detection");
|
const WipeTowerFootprint footprint = cur_plate->estimate_wipe_tower_footprint(m_print_config, filaments_cnt);
|
||||||
bool enable_wrapping = (wrapping_detection != nullptr) && wrapping_detection->value;
|
Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height);
|
||||||
|
|
||||||
Vec3d wipe_tower_size = cur_plate->estimate_wipe_tower_size(m_print_config, w, v, new_extruder_count, filaments_cnt, false, enable_wrapping);
|
|
||||||
Vec3d plate_origin = cur_plate->get_origin();
|
Vec3d plate_origin = cur_plate->get_origin();
|
||||||
int plate_width, plate_depth;
|
int plate_width, plate_depth;
|
||||||
double plate_height;
|
double plate_height;
|
||||||
partplate_list.get_plate_size(plate_width, plate_depth, plate_height);
|
partplate_list.get_plate_size(plate_width, plate_depth, plate_height);
|
||||||
float depth = wipe_tower_size(1);
|
float depth = wipe_tower_size(1);
|
||||||
float margin = 15.f, wp_brim_width = 0.f;
|
// Brim already resolved against the height the body was sized from.
|
||||||
ConfigOption *wipe_tower_brim_width_opt = m_print_config.option("prime_tower_brim_width");
|
float margin = 15.f, wp_brim_width = float(footprint.brim_width);
|
||||||
if (wipe_tower_brim_width_opt ) {
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%")%wp_brim_width;
|
||||||
wp_brim_width = wipe_tower_brim_width_opt->getFloat();
|
|
||||||
if (wp_brim_width < 0) wp_brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%")%wp_brim_width;
|
|
||||||
}
|
|
||||||
w = wipe_tower_size(0);
|
w = wipe_tower_size(0);
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: x=%1%, y=%2%, width=%3%, depth=%4%, angle=%5%, prime_volume=%6%, filaments_cnt=%7%, layer_height=%8%, plate_width=%9%, plate_depth=%10%")
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: x=%1%, y=%2%, width=%3%, depth=%4%, angle=%5%, prime_volume=%6%, filaments_cnt=%7%, layer_height=%8%, plate_width=%9%, plate_depth=%10%")
|
||||||
|
|||||||
@@ -272,6 +272,8 @@ set(lisbslic3r_sources
|
|||||||
GCode/WipeTower2.hpp
|
GCode/WipeTower2.hpp
|
||||||
GCode/WipeTower.cpp
|
GCode/WipeTower.cpp
|
||||||
GCode/WipeTower.hpp
|
GCode/WipeTower.hpp
|
||||||
|
GCode/WipeTowerEstimate.cpp
|
||||||
|
GCode/WipeTowerEstimate.hpp
|
||||||
GCodeWriter.cpp
|
GCodeWriter.cpp
|
||||||
GCodeWriter.hpp
|
GCodeWriter.hpp
|
||||||
Geometry/ArcWelder.hpp
|
Geometry/ArcWelder.hpp
|
||||||
|
|||||||
109
src/libslic3r/GCode/WipeTowerEstimate.cpp
Normal file
109
src/libslic3r/GCode/WipeTowerEstimate.cpp
Normal file
@@ -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
|
||||||
29
src/libslic3r/GCode/WipeTowerEstimate.hpp
Normal file
29
src/libslic3r/GCode/WipeTowerEstimate.hpp
Normal file
@@ -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
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "GCode.hpp"
|
#include "GCode.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
#include "GCode/WipeTower2.hpp"
|
#include "GCode/WipeTower2.hpp"
|
||||||
|
#include "GCode/WipeTowerEstimate.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "MaterialType.hpp"
|
#include "MaterialType.hpp"
|
||||||
@@ -1031,20 +1032,20 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
|||||||
|
|
||||||
//BBS: add the wipe tower check logic
|
//BBS: add the wipe tower check logic
|
||||||
const PrintConfig & config = print.config();
|
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();
|
int plate_index = print.get_plate_index();
|
||||||
const Vec3d plate_origin = print.get_plate_origin();
|
const Vec3d plate_origin = print.get_plate_origin();
|
||||||
float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0);
|
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 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 a = config.wipe_tower_rotation_angle.value;
|
||||||
//float v = config.wiping_volume.value;
|
//float v = config.wiping_volume.value;
|
||||||
|
|
||||||
float depth = print.wipe_tower_data(filaments_count).depth;
|
// The estimate resolves the effective width (a rib wall squares the tower).
|
||||||
//float brim_width = print.wipe_tower_data(filaments_count).brim_width;
|
const WipeTowerData &wipe_tower_estimate = print.wipe_tower_data(filaments_count);
|
||||||
|
float width = wipe_tower_estimate.width;
|
||||||
if (config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib)
|
float depth = wipe_tower_estimate.depth;
|
||||||
width = depth;
|
|
||||||
|
|
||||||
Polygons convex_hulls_temp;
|
Polygons convex_hulls_temp;
|
||||||
if (print.has_wipe_tower()) {
|
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
|
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.
|
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
||||||
double max_height = 0;
|
// validation cannot reject a position the clamp just accepted.
|
||||||
for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) {
|
if (is_step_done(psWipeTower) || filaments_cnt == 0)
|
||||||
double object_z = (double) m_objects[obj_idx]->size().z();
|
return m_wipe_tower_data;
|
||||||
max_height = std::max(unscale_(object_z), max_height);
|
|
||||||
|
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
|
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(m_config, filaments_cnt, layer_height, max_height, any_raft);
|
||||||
layer_height = m_objects.front()->config().layer_height.value;
|
WipeTowerData &data = const_cast<Print *>(this)->m_wipe_tower_data;
|
||||||
|
data.depth = float(footprint.depth);
|
||||||
auto timelapse_type = config().option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
data.width = float(footprint.width);
|
||||||
bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib);
|
data.brim_width = float(footprint.brim_width);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return m_wipe_tower_data;
|
return m_wipe_tower_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -782,6 +782,9 @@ struct WipeTowerData
|
|||||||
|
|
||||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||||
float depth;
|
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;
|
std::vector<std::pair<float, float>> z_and_depth_pairs;
|
||||||
float brim_width;
|
float brim_width;
|
||||||
float height;
|
float height;
|
||||||
@@ -795,6 +798,7 @@ struct WipeTowerData
|
|||||||
used_filament.clear();
|
used_filament.clear();
|
||||||
number_of_toolchanges = -1;
|
number_of_toolchanges = -1;
|
||||||
depth = 0.f;
|
depth = 0.f;
|
||||||
|
width = 0.f;
|
||||||
brim_width = 0.f;
|
brim_width = 0.f;
|
||||||
height = 0.f;
|
height = 0.f;
|
||||||
rib_offset = Vec2f::Zero();
|
rib_offset = Vec2f::Zero();
|
||||||
|
|||||||
@@ -2891,20 +2891,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
||||||
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
||||||
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
||||||
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
|
||||||
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||||
// BBS
|
|
||||||
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
|
||||||
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
||||||
|
|
||||||
const Print* print = m_process->fff_print();
|
|
||||||
const Print* current_print = part_plate->fff_print();
|
const Print* current_print = part_plate->fff_print();
|
||||||
if (!need_wipe_tower && part_plate->get_extruders(true).size() < 2) continue;
|
if (!need_wipe_tower && part_plate->get_extruders(true).size() < 2) continue;
|
||||||
if (part_plate->get_objects_on_this_plate().empty()) continue;
|
if (part_plate->get_objects_on_this_plate().empty()) continue;
|
||||||
|
|
||||||
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
|
// Body and brim from this plate's own estimate: m_process->fff_print() is the
|
||||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
// selected plate's, so an auto brim drew every tower with that plate's brim.
|
||||||
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
|
const WipeTowerFootprint footprint = part_plate->estimate_wipe_tower_footprint(full_config);
|
||||||
|
float brim_width = float(footprint.brim_width);
|
||||||
|
Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height);
|
||||||
|
|
||||||
// The stored position is already clamped onto the bed, by
|
// The stored position is already clamped onto the bed, by
|
||||||
// set_default_wipe_tower_pos_for_plate and again on every drag.
|
// set_default_wipe_tower_pos_for_plate and again on every drag.
|
||||||
|
|||||||
@@ -265,8 +265,7 @@ arrangement::ArrangePolygon estimate_wipe_tower_info(int plate_index, std::set<i
|
|||||||
int extruder_size = extruder_ids.size();
|
int extruder_size = extruder_ids.size();
|
||||||
|
|
||||||
Vec3d wipe_tower_size, wipe_tower_pos;
|
Vec3d wipe_tower_size, wipe_tower_pos;
|
||||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
auto arrange_poly = ppl.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(full_config, plate_index, wipe_tower_pos, wipe_tower_size, extruder_size);
|
||||||
auto arrange_poly = ppl.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(full_config, plate_index, wipe_tower_pos, wipe_tower_size, nozzle_nums, extruder_size);
|
|
||||||
arrange_poly.bed_idx = plate_index;
|
arrange_poly.bed_idx = plate_index;
|
||||||
return arrange_poly;
|
return arrange_poly;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
@@ -1531,8 +1532,15 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
|
|||||||
if (check_objects_empty_and_gcode3mf(plate_extruders)) {
|
if (check_objects_empty_and_gcode3mf(plate_extruders)) {
|
||||||
return plate_extruders;
|
return plate_extruders;
|
||||||
}
|
}
|
||||||
// if 3mf file
|
return get_extruders(conside_custom_gcode, wxGetApp().preset_bundle->prints.get_edited_preset().config, wxGetApp().preset_bundle->project_config);
|
||||||
const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
}
|
||||||
|
|
||||||
|
// The plate's filaments, with the global keys read from the given configs rather than the
|
||||||
|
// application's presets: the wipe tower estimate is also called under the CLI, which has no
|
||||||
|
// application object. get_extruders(bool) passes the edited presets; a full config serves both.
|
||||||
|
std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const
|
||||||
|
{
|
||||||
|
std::vector<int> plate_extruders;
|
||||||
int glb_support_intf_extr = glb_config.opt_int("support_interface_filament");
|
int glb_support_intf_extr = glb_config.opt_int("support_interface_filament");
|
||||||
int glb_support_extr = glb_config.opt_int("support_filament");
|
int glb_support_extr = glb_config.opt_int("support_filament");
|
||||||
int glb_outer_wall_extr = glb_config.opt_int("outer_wall_filament_id");
|
int glb_outer_wall_extr = glb_config.opt_int("outer_wall_filament_id");
|
||||||
@@ -1549,7 +1557,9 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
|
|||||||
glb_support |= glb_config.opt_int("raft_layers") > 0;
|
glb_support |= glb_config.opt_int("raft_layers") > 0;
|
||||||
|
|
||||||
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
||||||
if (!contain_instance_totally(obj_idx, 0))
|
// Any instance on the plate counts, as PrintApply does: after an arrange, instance 0
|
||||||
|
// can sit on a different plate.
|
||||||
|
if (!contain_any_instance_totally(obj_idx))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ModelObject* mo = m_model->objects[obj_idx];
|
ModelObject* mo = m_model->objects[obj_idx];
|
||||||
@@ -1662,7 +1672,7 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
|
|||||||
if (conside_custom_gcode) {
|
if (conside_custom_gcode) {
|
||||||
//BBS
|
//BBS
|
||||||
int nums_extruders = 0;
|
int nums_extruders = 0;
|
||||||
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(wxGetApp().preset_bundle->project_config.option("filament_colour"))) {
|
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(project_config.option("filament_colour"))) {
|
||||||
nums_extruders = color_option->values.size();
|
nums_extruders = color_option->values.size();
|
||||||
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
|
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
|
||||||
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
|
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
|
||||||
@@ -1681,9 +1691,8 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
|
|||||||
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
|
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
|
||||||
// physical filaments it resolves to instead.
|
// physical filaments it resolves to instead.
|
||||||
{
|
{
|
||||||
auto& project_config = wxGetApp().preset_bundle->project_config;
|
const auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||||
auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
const auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||||
auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
|
||||||
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
|
||||||
std::vector<unsigned int> ext_0based;
|
std::vector<unsigned int> ext_0based;
|
||||||
for (int e : plate_extruders)
|
for (int e : plate_extruders)
|
||||||
@@ -2311,113 +2320,86 @@ bool PartPlate::check_compatible_of_nozzle_and_filament(const DynamicPrintConfig
|
|||||||
return wipe_tower_size;
|
return wipe_tower_size;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, const double w, const double wipe_volume, int extruder_count, int plate_extruder_size, bool use_global_objects, bool enable_wrapping_detection) const
|
WipeTowerFootprint PartPlate::estimate_wipe_tower_footprint(const DynamicPrintConfig &config, int plate_extruder_size, bool use_global_objects) const
|
||||||
{
|
{
|
||||||
Vec3d wipe_tower_size;
|
// The CLI calls this too, so the plate's filaments are derived from the passed config:
|
||||||
double layer_height = 0.08f; // hard code layer height
|
// get_extruders(bool) reads the same keys off wxGetApp()'s presets, which the CLI has none of.
|
||||||
double max_height = 0.f;
|
std::vector<int> plate_extruders;
|
||||||
wipe_tower_size.setZero();
|
if (plate_extruder_size == 0) {
|
||||||
|
plate_extruders = get_extruders(true, config, config);
|
||||||
const ConfigOption* layer_height_opt = config.option("layer_height");
|
plate_extruder_size = int(plate_extruders.size());
|
||||||
if (layer_height_opt)
|
}
|
||||||
layer_height = layer_height_opt->getFloat();
|
// The wipe tower filament joins the tool ordering even when unused (Print::extruders), so
|
||||||
|
// validation counts it. An explicit count is the plate's painted filaments, which never do.
|
||||||
// empty plate
|
const ConfigOption *wipe_tower_filament_opt = config.option("wipe_tower_filament");
|
||||||
if (plate_extruder_size == 0)
|
const int wipe_tower_filament = wipe_tower_filament_opt != nullptr ? wipe_tower_filament_opt->getInt() : 0;
|
||||||
{
|
if (plate_extruder_size > 1 && wipe_tower_filament > 0) {
|
||||||
std::vector<int> plate_extruders = get_extruders(true);
|
if (plate_extruders.empty())
|
||||||
plate_extruder_size = plate_extruders.size();
|
plate_extruders = get_extruders(true, config, config);
|
||||||
|
if (std::find(plate_extruders.begin(), plate_extruders.end(), wipe_tower_filament) == plate_extruders.end())
|
||||||
|
++plate_extruder_size;
|
||||||
}
|
}
|
||||||
if (plate_extruder_size == 0)
|
if (plate_extruder_size == 0)
|
||||||
return wipe_tower_size;
|
return WipeTowerFootprint();
|
||||||
|
|
||||||
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
// Tallest object on this plate and the thinnest layer it is sliced at, resolved per object
|
||||||
if (!use_global_objects && !contain_instance_totally(obj_idx, 0))
|
// as PrintObject resolves them (override, else preset) and over this plate's objects only -
|
||||||
|
// seeding from the global value, or folding in an off-plate override, diverges from Print.
|
||||||
|
const ConfigOption *layer_height_opt = config.option("layer_height");
|
||||||
|
const double global_layer_height = layer_height_opt != nullptr ? layer_height_opt->getFloat() : 0.08;
|
||||||
|
const ConfigOption *raft_layers_opt = config.option("raft_layers");
|
||||||
|
const int global_raft_layers = raft_layers_opt != nullptr ? raft_layers_opt->getInt() : 0;
|
||||||
|
double max_height = 0.;
|
||||||
|
double layer_height = std::numeric_limits<double>::max();
|
||||||
|
bool any_raft = false;
|
||||||
|
for (int obj_idx = 0; obj_idx < int(m_model->objects.size()); ++obj_idx) {
|
||||||
|
const ModelObject *object = m_model->objects[obj_idx];
|
||||||
|
if (!use_global_objects && !contain_any_instance_totally(obj_idx))
|
||||||
continue;
|
continue;
|
||||||
|
// Per instance, to match PrintObject::size(); the union over instances differs once
|
||||||
BoundingBoxf3 bbox = m_model->objects[obj_idx]->bounding_box_exact();
|
// they are rotated apart.
|
||||||
max_height = std::max(bbox.size().z(), max_height);
|
for (int inst_idx = 0; inst_idx < int(object->instances.size()); ++inst_idx) {
|
||||||
}
|
if (!use_global_objects && !contain_instance_totally(obj_idx, inst_idx))
|
||||||
wipe_tower_size(2) = max_height;
|
continue;
|
||||||
//const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
max_height = std::max(max_height, object->instance_bounding_box(inst_idx, true).size().z());
|
||||||
auto timelapse_type = config.option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
|
||||||
bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | enable_wrapping_detection;
|
|
||||||
double extra_spacing = config.option("prime_tower_infill_gap")->getFloat() / 100.;
|
|
||||||
const ConfigOptionEnum<WipeTowerWallType>* use_rib_wall_opt = config.option<ConfigOptionEnum<WipeTowerWallType>>("wipe_tower_wall_type");
|
|
||||||
bool use_rib_wall = use_rib_wall_opt ? use_rib_wall_opt->value == WipeTowerWallType::wtwRib: false;
|
|
||||||
double rib_width = config.option("wipe_tower_rib_width")->getFloat();
|
|
||||||
double depth;
|
|
||||||
double filament_change_volume=0.;
|
|
||||||
{
|
|
||||||
std::vector<double> filament_change_lengths;
|
|
||||||
auto filament_change_lengths_opt = m_print->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 = m_print->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.;
|
|
||||||
}
|
|
||||||
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
|
|
||||||
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
|
|
||||||
// Read from the passed plate config — m_print may not have been applied yet
|
|
||||||
// (fresh plates, CLI), in which case its PrintConfig still holds defaults.
|
|
||||||
const auto *purge_opt = config.option<ConfigOptionBool>("purge_in_prime_tower");
|
|
||||||
const auto *semm_opt = config.option<ConfigOptionBool>("single_extruder_multi_material");
|
|
||||||
const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value;
|
|
||||||
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size);
|
|
||||||
if (use_rib_wall) {
|
|
||||||
depth = std::sqrt(volume / layer_height * extra_spacing);
|
|
||||||
if (need_wipe_tower || plate_extruder_size > 1) {
|
|
||||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
|
||||||
double volume_depth = depth;
|
|
||||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
|
||||||
rib_width = std::min(rib_width, depth / 2);
|
|
||||||
depth = rib_width / std::sqrt(2) + std::max(depth + m_print->config().wipe_tower_extra_rib_length.value, volume_depth);
|
|
||||||
wipe_tower_size(0) = wipe_tower_size(1) = depth;
|
|
||||||
}
|
}
|
||||||
|
const ConfigOption *object_layer_height = object->config.option("layer_height");
|
||||||
|
layer_height = std::min(layer_height, object_layer_height != nullptr ? object_layer_height->getFloat() : global_layer_height);
|
||||||
|
const ConfigOption *object_raft_layers = object->config.option("raft_layers");
|
||||||
|
any_raft = any_raft || (object_raft_layers != nullptr ? object_raft_layers->getInt() : global_raft_layers) > 0;
|
||||||
}
|
}
|
||||||
else {
|
if (layer_height == std::numeric_limits<double>::max())
|
||||||
depth = volume / (layer_height * w);
|
layer_height = global_layer_height;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
wipe_tower_size(0) = w;
|
|
||||||
wipe_tower_size(1) = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wipe_tower_size;
|
return Slic3r::estimate_wipe_tower_footprint(config, size_t(plate_extruder_size), layer_height, max_height, any_raft);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const DynamicPrintConfig& config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int extruder_count, int plate_extruder_size, bool use_global_objects) const
|
Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig &config, int plate_extruder_size, bool use_global_objects) const
|
||||||
|
{
|
||||||
|
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(config, plate_extruder_size, use_global_objects);
|
||||||
|
return Vec3d(footprint.width, footprint.depth, footprint.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const DynamicPrintConfig& config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int plate_extruder_size, bool use_global_objects) const
|
||||||
{
|
{
|
||||||
float x = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_x"))->get_at(plate_index);
|
float x = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_x"))->get_at(plate_index);
|
||||||
float y = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_y"))->get_at(plate_index);
|
float y = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_y"))->get_at(plate_index);
|
||||||
float w = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_tower_width"))->value;
|
|
||||||
//float a = dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value;
|
//float a = dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value;
|
||||||
float v = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_volume"))->value;
|
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(config, plate_extruder_size, use_global_objects);
|
||||||
float tower_brim_width = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_tower_brim_width"))->value;
|
wt_size = Vec3d(footprint.width, footprint.depth, footprint.height);
|
||||||
const ConfigOptionBool * wrapping_opt = dynamic_cast<const ConfigOptionBool *>(config.option("enable_wrapping_detection"));
|
|
||||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
|
||||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
|
||||||
int plate_width=m_width, plate_depth=m_depth;
|
int plate_width=m_width, plate_depth=m_depth;
|
||||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
float w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
||||||
float depth = wt_size(1);
|
float depth = wt_size(1);
|
||||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
// Resolved brim, not the raw option: "Auto" (-1) would yield a margin of 0 and let the
|
||||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
// clamp put the brim off the bed. Matches set_default_wipe_tower_pos_for_plate.
|
||||||
if (wipe_tower_brim_width_opt) {
|
const float wp_brim_width = float(footprint.brim_width);
|
||||||
wp_brim_width = wipe_tower_brim_width_opt->getFloat();
|
const float margin = WIPE_TOWER_MARGIN + wp_brim_width;
|
||||||
if (wp_brim_width < 0) wp_brim_width = WipeTower::get_auto_brim_by_height((float) wt_size.z());
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%") % wp_brim_width;
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%") % wp_brim_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = std::clamp(x, margin, (float)plate_width - w - margin - wp_brim_width);
|
// A tower too deep for the plate leaves no valid position: clamping with hi < lo is UB and
|
||||||
y = std::clamp(y, margin, (float)plate_depth - depth - margin - wp_brim_width);
|
// in release silently returns the negative hi.
|
||||||
|
x = std::clamp(x, margin, std::max(margin, (float)plate_width - w - margin));
|
||||||
|
y = std::clamp(y, margin, std::max(margin, (float)plate_depth - depth - margin));
|
||||||
wt_pos(0) = x;
|
wt_pos(0) = x;
|
||||||
wt_pos(1) = y;
|
wt_pos(1) = y;
|
||||||
wt_pos(2) = 0.f;
|
wt_pos(2) = 0.f;
|
||||||
@@ -2755,6 +2737,20 @@ bool PartPlate::contain_instance_totally(int obj_id, int instance_id) const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//judge whether any of the object's instances is totally included in plate or not
|
||||||
|
bool PartPlate::contain_any_instance_totally(int obj_id) const
|
||||||
|
{
|
||||||
|
if (obj_id < 0 || obj_id >= int(m_model->objects.size()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ModelObject *object = m_model->objects[obj_id];
|
||||||
|
for (int instance_id = 0; instance_id < int(object->instances.size()); ++instance_id)
|
||||||
|
if (contain_instance_totally(obj_id, instance_id))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//check whether instance is outside the plate or not
|
//check whether instance is outside the plate or not
|
||||||
bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* bounding_box)
|
bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* bounding_box)
|
||||||
{
|
{
|
||||||
@@ -4488,26 +4484,16 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
|||||||
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
||||||
}
|
}
|
||||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
||||||
float w = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_tower_width"))->value;
|
WipeTowerFootprint footprint = part_plate->estimate_wipe_tower_footprint(full_config, init_pos ? 2 : 0);
|
||||||
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
|
|
||||||
bool enable_wrapping = false;
|
|
||||||
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
|
|
||||||
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
|
|
||||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
|
||||||
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
|
|
||||||
|
|
||||||
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
|
if (!init_pos && (is_approx(footprint.width, 0.0) || is_approx(footprint.depth, 0.0))) {
|
||||||
wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping);
|
footprint = part_plate->estimate_wipe_tower_footprint(full_config, 2);
|
||||||
}
|
}
|
||||||
|
Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height);
|
||||||
|
|
||||||
// Compute brim-aware margin: brim extends outward from tower position
|
// Brim-aware margin: the brim extends outward from the tower position.
|
||||||
float brim_width = 0.f;
|
const float brim_width = float(footprint.brim_width);
|
||||||
const ConfigOptionFloat *brim_opt = full_config.option<ConfigOptionFloat>("prime_tower_brim_width");
|
const float margin = WIPE_TOWER_MARGIN + brim_width;
|
||||||
if (brim_opt) {
|
|
||||||
brim_width = brim_opt->value;
|
|
||||||
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
|
|
||||||
}
|
|
||||||
const float margin = WIPE_TOWER_MARGIN + brim_width;
|
|
||||||
|
|
||||||
// clamp wipe tower position within plate boundaries
|
// clamp wipe tower position within plate boundaries
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||||
#include "libslic3r/Slicing.hpp"
|
#include "libslic3r/Slicing.hpp"
|
||||||
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
#include "libslic3r/Arrange.hpp"
|
#include "libslic3r/Arrange.hpp"
|
||||||
#include "Plater.hpp"
|
#include "Plater.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
@@ -339,11 +340,14 @@ public:
|
|||||||
|
|
||||||
Vec3d get_origin() { return m_origin; }
|
Vec3d get_origin() { return m_origin; }
|
||||||
//Vec3d calculate_wipe_tower_size(const DynamicPrintConfig &config, const double w, const double wipe_volume, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
//Vec3d calculate_wipe_tower_size(const DynamicPrintConfig &config, const double w, const double wipe_volume, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
||||||
Vec3d estimate_wipe_tower_size(const DynamicPrintConfig & config, const double w, const double wipe_volume, int extruder_count = 1, int plate_extruder_size = 0, bool use_global_objects = false, bool enable_wrapping_detection = false) const;
|
// plate_extruder_size: filaments purged on the plate; 0 derives it from the plate's objects.
|
||||||
arrangement::ArrangePolygon estimate_wipe_tower_polygon(const DynamicPrintConfig & config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int extruder_count = 1, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
WipeTowerFootprint estimate_wipe_tower_footprint(const DynamicPrintConfig & config, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
||||||
|
Vec3d estimate_wipe_tower_size(const DynamicPrintConfig & config, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
||||||
|
arrangement::ArrangePolygon estimate_wipe_tower_polygon(const DynamicPrintConfig & config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int plate_extruder_size = 0, bool use_global_objects = false) const;
|
||||||
bool check_objects_empty_and_gcode3mf(std::vector<int> &result) const;
|
bool check_objects_empty_and_gcode3mf(std::vector<int> &result) const;
|
||||||
// get used filaments from config, 1 based idx
|
// get used filaments from config, 1 based idx
|
||||||
std::vector<int> get_extruders(bool conside_custom_gcode = false) const;
|
std::vector<int> get_extruders(bool conside_custom_gcode = false) const;
|
||||||
|
std::vector<int> get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const;
|
||||||
std::vector<int> get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const;
|
std::vector<int> get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const;
|
||||||
std::vector<int> get_extruders_without_support(bool conside_custom_gcode = false) const;
|
std::vector<int> get_extruders_without_support(bool conside_custom_gcode = false) const;
|
||||||
// get used filaments from gcode result, 1 based idx
|
// get used filaments from gcode result, 1 based idx
|
||||||
@@ -366,6 +370,8 @@ public:
|
|||||||
bool contain_instance_totally(ModelObject* object, int instance_id) const;
|
bool contain_instance_totally(ModelObject* object, int instance_id) const;
|
||||||
//judge whether instance is totally included in plate or not
|
//judge whether instance is totally included in plate or not
|
||||||
bool contain_instance_totally(int obj_id, int instance_id) const;
|
bool contain_instance_totally(int obj_id, int instance_id) const;
|
||||||
|
//judge whether any of the object's instances is totally included in plate or not
|
||||||
|
bool contain_any_instance_totally(int obj_id) const;
|
||||||
|
|
||||||
//judge whether the plate's origin is at the left of instance or not
|
//judge whether the plate's origin is at the left of instance or not
|
||||||
bool is_left_top_of(int obj_id, int instance_id);
|
bool is_left_top_of(int obj_id, int instance_id);
|
||||||
|
|||||||
@@ -182,3 +182,86 @@ TEST_CASE("The wipe tower's toolchange planner flush follows the gcode flavor",
|
|||||||
CHECK_THAT(tower, !Catch::Matchers::ContainsSubstring(unexpected));
|
CHECK_THAT(tower, !Catch::Matchers::ContainsSubstring(unexpected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What Print feeds the shared estimate. The libslic3r WipeTowerEstimate cases cannot see this:
|
||||||
|
// they call the estimator directly.
|
||||||
|
static DynamicPrintConfig tower_estimate_config(const char *wall_type)
|
||||||
|
{
|
||||||
|
// 100 mm3 per purge on a 50 mm wide tower: one purge is 100/(layer_height * 50) of depth.
|
||||||
|
return multifilament_config(2, {
|
||||||
|
{ "enable_prime_tower", "1" },
|
||||||
|
{ "wipe_tower_wall_type", wall_type },
|
||||||
|
{ "prime_tower_width", "50" },
|
||||||
|
{ "prime_volume", "100" },
|
||||||
|
{ "prime_tower_infill_gap", "100%" },
|
||||||
|
{ "prime_tower_brim_width", "3" },
|
||||||
|
{ "purge_in_prime_tower", "0" },
|
||||||
|
{ "single_extruder_multi_material", "0" },
|
||||||
|
{ "timelapse_type", "0" },
|
||||||
|
{ "layer_height", "0.2" },
|
||||||
|
{ "raft_layers", "0" } });
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The tower is sized for the thinnest layer any object on the plate is sliced at", "[WipeTower]")
|
||||||
|
{
|
||||||
|
// The tower has to survive its thinnest layer, so an override finer than the preset drives
|
||||||
|
// the estimate even on the second object. Two 20 mm cubes, the second at 0.1 mm.
|
||||||
|
const DynamicPrintConfig config = tower_estimate_config("rectangle");
|
||||||
|
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides = {
|
||||||
|
{}, { { "layer_height", "0.1" } } };
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
init_print({ cube(20), cube(20) }, print, model, config, &overrides);
|
||||||
|
|
||||||
|
// One purge at 0.1 mm: 100 / (0.1 * 50) = 20 mm, above the 20 mm-tall tower's stability
|
||||||
|
// floor. At the preset's 0.2 mm it would be half that, so the two are easy to tell apart.
|
||||||
|
const float floor_20mm = WipeTower::get_limit_depth_by_height(20.f);
|
||||||
|
REQUIRE(floor_20mm < 10.f);
|
||||||
|
CHECK_THAT(print.wipe_tower_data(2).depth, Catch::Matchers::WithinAbs(20., 1e-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Validation is given the tower's effective width, not the configured one", "[WipeTower]")
|
||||||
|
{
|
||||||
|
// A rib wall squares the tower, so its width is its depth. Validation reads this rather
|
||||||
|
// than re-deriving the rule from the wall type.
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
|
||||||
|
SECTION("a rectangle wall keeps the configured width") {
|
||||||
|
const DynamicPrintConfig config = tower_estimate_config("rectangle");
|
||||||
|
init_print({ cube(20) }, print, model, config);
|
||||||
|
const WipeTowerData &data = print.wipe_tower_data(2);
|
||||||
|
CHECK_THAT(data.width, Catch::Matchers::WithinAbs(50., 1e-4));
|
||||||
|
CHECK(data.depth < data.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a rib wall reports the squared footprint") {
|
||||||
|
const DynamicPrintConfig config = tower_estimate_config("rib");
|
||||||
|
init_print({ cube(20) }, print, model, config);
|
||||||
|
const WipeTowerData &data = print.wipe_tower_data(2);
|
||||||
|
CHECK_THAT(data.width, Catch::Matchers::WithinAbs(data.depth, 1e-4));
|
||||||
|
CHECK(data.width > 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A single-filament plate reserves a tower only when one is actually printed", "[WipeTower]")
|
||||||
|
{
|
||||||
|
// Reporting no tower for one that is built collapses the validation hull to a point, so
|
||||||
|
// the config-visible reasons for a single-filament tower have to be honoured.
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
|
||||||
|
SECTION("no tool change and nothing else that prints one") {
|
||||||
|
const DynamicPrintConfig config = tower_estimate_config("rib");
|
||||||
|
init_print({ cube(20) }, print, model, config);
|
||||||
|
CHECK_THAT(print.wipe_tower_data(1).depth, Catch::Matchers::WithinAbs(0., 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a raft puts the tower on every layer below the object") {
|
||||||
|
DynamicPrintConfig config = tower_estimate_config("rib");
|
||||||
|
config.set_deserialize_strict({ { "raft_layers", "3" } });
|
||||||
|
init_print({ cube(20) }, print, model, config);
|
||||||
|
CHECK(print.wipe_tower_data(1).depth > 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_utils.cpp
|
test_utils.cpp
|
||||||
test_timeutils.cpp
|
test_timeutils.cpp
|
||||||
test_voronoi.cpp
|
test_voronoi.cpp
|
||||||
|
test_wipe_tower_estimate.cpp
|
||||||
test_optimizers.cpp
|
test_optimizers.cpp
|
||||||
test_ordering_strategies.cpp
|
test_ordering_strategies.cpp
|
||||||
# test_png_io.cpp
|
# test_png_io.cpp
|
||||||
|
|||||||
206
tests/libslic3r/test_wipe_tower_estimate.cpp
Normal file
206
tests/libslic3r/test_wipe_tower_estimate.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/GCode/WipeTower.hpp"
|
||||||
|
#include "libslic3r/GCode/WipeTower2.hpp"
|
||||||
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
using Catch::Matchers::WithinAbs;
|
||||||
|
|
||||||
|
// Rectangle wall, one nozzle, 100 mm3 prime volume on a 50 mm wide tower at 0.2 mm layers: one
|
||||||
|
// purge is 10 mm of depth. The flush matrix is off here; the shipped-default case covers it.
|
||||||
|
// Built as PresetBundle::full_config builds the GUI's: apply() creates each enum as a
|
||||||
|
// ConfigOptionEnumGeneric, where full_print_config() would clone the static defaults'
|
||||||
|
// ConfigOptionEnum<T>. The estimate has to read either.
|
||||||
|
static DynamicPrintConfig preset_shaped_defaults()
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.apply(FullPrintConfig::defaults());
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DynamicPrintConfig make_config(const char *wall_type = "rectangle")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = preset_shaped_defaults();
|
||||||
|
config.set_key_value("prime_tower_width", new ConfigOptionFloat(50.));
|
||||||
|
config.set_key_value("prime_volume", new ConfigOptionFloat(100.));
|
||||||
|
config.set_key_value("prime_tower_infill_gap", new ConfigOptionPercent(100.));
|
||||||
|
config.set_key_value("prime_tower_brim_width", new ConfigOptionFloat(3.));
|
||||||
|
config.set_deserialize_strict("wipe_tower_wall_type", wall_type);
|
||||||
|
config.set_key_value("wipe_tower_rib_width", new ConfigOptionFloat(8.));
|
||||||
|
config.set_key_value("wipe_tower_extra_rib_length", new ConfigOptionFloat(0.));
|
||||||
|
config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4}));
|
||||||
|
config.set_deserialize_strict("timelapse_type", "0");
|
||||||
|
config.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||||
|
config.set_key_value("raft_layers", new ConfigOptionInt(0));
|
||||||
|
config.set_key_value("purge_in_prime_tower", new ConfigOptionBool(false));
|
||||||
|
config.set_key_value("single_extruder_multi_material", new ConfigOptionBool(false));
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A rectangle wall tower is sized by the purge volume", "[WipeTowerEstimate]") {
|
||||||
|
const DynamicPrintConfig config = make_config();
|
||||||
|
// Three filaments purge twice per layer; a 5 mm object keeps the stability floor at 5 mm.
|
||||||
|
const WipeTowerFootprint fp = estimate_wipe_tower_footprint(config, 3, 0.2, 5., false);
|
||||||
|
CHECK_THAT(fp.width, WithinAbs(50., 1e-9));
|
||||||
|
CHECK_THAT(fp.depth, WithinAbs(20., 1e-9));
|
||||||
|
CHECK_THAT(fp.height, WithinAbs(5., 1e-9));
|
||||||
|
CHECK_THAT(fp.brim_width, WithinAbs(3., 1e-9));
|
||||||
|
// Thinner layers need more depth for the same volume.
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 3, 0.1, 5., false).depth, WithinAbs(40., 1e-9));
|
||||||
|
// The infill gap spaces the purge lines.
|
||||||
|
DynamicPrintConfig spaced = config;
|
||||||
|
spaced.set_key_value("prime_tower_infill_gap", new ConfigOptionPercent(150.));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(spaced, 3, 0.2, 5., false).depth, WithinAbs(30., 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Object height sets the stability floor and the auto brim", "[WipeTowerEstimate]") {
|
||||||
|
DynamicPrintConfig config = make_config();
|
||||||
|
// Two filaments purge once: 10 mm, lifted to the 20 mm floor of a 100 mm tower.
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 100., false).depth, WithinAbs(20., 1e-9));
|
||||||
|
config.set_key_value("prime_tower_brim_width", new ConfigOptionFloat(-1.));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 50., false).brim_width, WithinAbs(WipeTower::get_auto_brim_by_height(50.f), 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A single filament only gets a tower when one is printed anyway", "[WipeTowerEstimate]") {
|
||||||
|
DynamicPrintConfig config = make_config();
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100., false).depth, WithinAbs(0., 1e-9));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 0, 0.2, 100., false).width, WithinAbs(0., 1e-9));
|
||||||
|
|
||||||
|
// Wrapping detection prints a tower on the first layers whatever the filament count.
|
||||||
|
config.set_key_value("enable_wrapping_detection", new ConfigOptionBool(true));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100., false).depth, WithinAbs(20., 1e-9));
|
||||||
|
config.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
// So does a raft. raft_layers is a per-object key, so it arrives as a resolved flag and
|
||||||
|
// is deliberately not read off the config.
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100., true).depth, WithinAbs(20., 1e-9));
|
||||||
|
config.set_key_value("raft_layers", new ConfigOptionInt(3));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100., false).depth, WithinAbs(0., 1e-9));
|
||||||
|
config.set_key_value("raft_layers", new ConfigOptionInt(0));
|
||||||
|
|
||||||
|
config.set_deserialize_strict("timelapse_type", "1");
|
||||||
|
// Smooth timelapse primes the single filament once: 10 mm, lifted to the floor.
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100., false).depth, WithinAbs(20., 1e-9));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 5., false).depth, WithinAbs(10., 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Both wall types agree on whether there is a tower at all", "[WipeTowerEstimate]") {
|
||||||
|
// A wall type may only change the shape of the tower, never whether one is reserved:
|
||||||
|
// reporting no tower for one that is built collapses the validation hull to a point.
|
||||||
|
const double height = GENERATE(5., 100.);
|
||||||
|
DynamicPrintConfig rect = make_config();
|
||||||
|
DynamicPrintConfig rib = make_config("rib");
|
||||||
|
|
||||||
|
// No tool change and nothing else that prints a tower - neither wall type reserves one.
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(rect, 1, 0.2, height, false).depth, WithinAbs(0., 1e-9));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(rib, 1, 0.2, height, false).depth, WithinAbs(0., 1e-9));
|
||||||
|
|
||||||
|
// Not even on a dual-nozzle printer, where a lone filament still needs no purge.
|
||||||
|
rect.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.4}));
|
||||||
|
rib.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.4}));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(rect, 1, 0.2, height, false).depth, WithinAbs(0., 1e-9));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(rib, 1, 0.2, height, false).depth, WithinAbs(0., 1e-9));
|
||||||
|
|
||||||
|
// With a tool change both reserve one, and both respect the stability floor.
|
||||||
|
CHECK(estimate_wipe_tower_footprint(rect, 2, 0.2, height, false).depth >= WipeTower::get_limit_depth_by_height(float(height)));
|
||||||
|
CHECK(estimate_wipe_tower_footprint(rib, 2, 0.2, height, false).depth >= WipeTower::get_limit_depth_by_height(float(height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A rib wall squares the tower and caps the rib width", "[WipeTowerEstimate]") {
|
||||||
|
DynamicPrintConfig config = make_config("rib");
|
||||||
|
// sqrt(200 / 0.2) = 31.62 mm square, plus the 8 mm rib bulge along the diagonal.
|
||||||
|
const double body = std::sqrt(1000.);
|
||||||
|
WipeTowerFootprint fp = estimate_wipe_tower_footprint(config, 3, 0.2, 5., false);
|
||||||
|
CHECK_THAT(fp.depth, WithinAbs(8. / std::sqrt(2.) + body, 1e-9));
|
||||||
|
CHECK_THAT(fp.width, WithinAbs(fp.depth, 1e-9));
|
||||||
|
// The extra rib length grows the footprint.
|
||||||
|
config.set_key_value("wipe_tower_extra_rib_length", new ConfigOptionFloat(4.));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 3, 0.2, 5., false).depth, WithinAbs(8. / std::sqrt(2.) + body + 4., 1e-9));
|
||||||
|
// A tiny tower caps the rib width at half its depth: 5 mm body, 2.5 mm rib.
|
||||||
|
config.set_key_value("wipe_tower_extra_rib_length", new ConfigOptionFloat(0.));
|
||||||
|
config.set_key_value("prime_volume", new ConfigOptionFloat(5.));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5., false).depth, WithinAbs(2.5 / std::sqrt(2.) + 5., 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Every wall and tower type is read the same from a preset and a static config", "[WipeTowerEstimate]") {
|
||||||
|
// The GUI, arrange and the CLI pass a DynamicPrintConfig whose enums are
|
||||||
|
// ConfigOptionEnumGeneric; Print passes a static config whose enums are ConfigOptionEnum<T>.
|
||||||
|
// The wall type is read by value, so both give the same shape, and the wipe tower
|
||||||
|
// implementation is not an input to the footprint at all.
|
||||||
|
const char *wall_type = GENERATE("rectangle", "cone", "rib");
|
||||||
|
const char *tower_type = GENERATE("type1", "type2");
|
||||||
|
DynamicPrintConfig preset = make_config(wall_type);
|
||||||
|
preset.set_deserialize_strict("wipe_tower_type", tower_type);
|
||||||
|
REQUIRE(dynamic_cast<const ConfigOptionEnumGeneric *>(preset.option("wipe_tower_wall_type")) != nullptr);
|
||||||
|
|
||||||
|
FullPrintConfig static_config;
|
||||||
|
static_config.apply(preset, true);
|
||||||
|
REQUIRE(static_config.wipe_tower_wall_type.serialize() == wall_type);
|
||||||
|
REQUIRE(static_config.wipe_tower_type.serialize() == tower_type);
|
||||||
|
|
||||||
|
// Three filaments purge twice per layer on a 5 mm object: a 50 x 20 rectangle, or a square.
|
||||||
|
const WipeTowerFootprint fp = estimate_wipe_tower_footprint(preset, 3, 0.2, 5., false);
|
||||||
|
if (std::string(wall_type) == "rib") {
|
||||||
|
CHECK_THAT(fp.width, WithinAbs(fp.depth, 1e-9));
|
||||||
|
CHECK_THAT(fp.depth, WithinAbs(8. / std::sqrt(2.) + std::sqrt(1000.), 1e-9));
|
||||||
|
} else {
|
||||||
|
CHECK_THAT(fp.width, WithinAbs(50., 1e-9));
|
||||||
|
CHECK_THAT(fp.depth, WithinAbs(20., 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
const WipeTowerFootprint from_static = estimate_wipe_tower_footprint(static_config, 3, 0.2, 5., false);
|
||||||
|
CHECK_THAT(from_static.width, WithinAbs(fp.width, 1e-9));
|
||||||
|
CHECK_THAT(from_static.depth, WithinAbs(fp.depth, 1e-9));
|
||||||
|
CHECK_THAT(from_static.brim_width, WithinAbs(fp.brim_width, 1e-9));
|
||||||
|
|
||||||
|
// Smooth timelapse is the other enum the estimate reads: a lone filament gets a tower
|
||||||
|
// through both storages too.
|
||||||
|
preset.set_deserialize_strict("timelapse_type", "1");
|
||||||
|
static_config.apply(preset, true);
|
||||||
|
CHECK(estimate_wipe_tower_footprint(preset, 1, 0.2, 5., false).depth > 0.);
|
||||||
|
CHECK(estimate_wipe_tower_footprint(static_config, 1, 0.2, 5., false).depth > 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A dual nozzle purges every filament plus the filament change", "[WipeTowerEstimate]") {
|
||||||
|
DynamicPrintConfig config = make_config();
|
||||||
|
config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.4}));
|
||||||
|
config.set_key_value("filament_change_length", new ConfigOptionFloats({10., 10.}));
|
||||||
|
config.set_key_value("filament_diameter", new ConfigOptionFloats({1.75, 1.75}));
|
||||||
|
// Two purges of 100 mm3 plus one 10 mm filament change: (200 + 10 * pi * 1.75^2 / 4) / (0.2 * 50).
|
||||||
|
const double change_volume = 10. * PI * 1.75 * 1.75 / 4.;
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5., false).depth, WithinAbs((200. + change_volume) / 10., 1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The shipped defaults size the tower from the flush matrix", "[WipeTowerEstimate]") {
|
||||||
|
// Both keys default to true, so the shipped configuration purges the flush volumes rather
|
||||||
|
// than the prime volume, with no infill gap on top - the flush volumes already hold it.
|
||||||
|
DynamicPrintConfig config = preset_shaped_defaults();
|
||||||
|
REQUIRE(config.opt_bool("purge_in_prime_tower"));
|
||||||
|
REQUIRE(config.opt_bool("single_extruder_multi_material"));
|
||||||
|
config.set_key_value("prime_tower_width", new ConfigOptionFloat(50.));
|
||||||
|
config.set_deserialize_strict("wipe_tower_wall_type", "rectangle");
|
||||||
|
config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4}));
|
||||||
|
|
||||||
|
const double flush_volume = WipeTower2::estimate_semm_flush_volume(config, 2);
|
||||||
|
const double expected = std::max(double(WipeTower::get_limit_depth_by_height(5.f)), flush_volume / (0.2 * 50.));
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5., false).depth, WithinAbs(expected, 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A config missing a tower key falls back to that key's default", "[WipeTowerEstimate]") {
|
||||||
|
// The signature takes any ConfigBase: an absent key must read as its declared default.
|
||||||
|
const DynamicPrintConfig full = make_config();
|
||||||
|
DynamicPrintConfig partial = full;
|
||||||
|
partial.erase("prime_tower_infill_gap");
|
||||||
|
REQUIRE(partial.option("prime_tower_infill_gap") == nullptr);
|
||||||
|
|
||||||
|
DynamicPrintConfig defaulted = full;
|
||||||
|
defaulted.set_key_value("prime_tower_infill_gap",
|
||||||
|
print_config_def.get("prime_tower_infill_gap")->default_value->clone());
|
||||||
|
CHECK_THAT(estimate_wipe_tower_footprint(partial, 3, 0.2, 5., false).depth,
|
||||||
|
WithinAbs(estimate_wipe_tower_footprint(defaulted, 3, 0.2, 5., false).depth, 1e-9));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user