mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 19:17:33 +00:00
Fix review findings in the shared wipe tower estimate
A raft is not a reason to reserve a tower. Print::apply runs normalize_fdm_2, which clears enable_prime_tower for a plate that purges one filament unless smooth timelapse or wrapping detection is on, so a single-filament plate with a raft prints no tower at all and the estimate was reserving bed area for one. Drop the input; need_wipe_tower is now exactly the two exceptions normalize_fdm_2 honours, named there so the next reason added has to be checked against it. The GUI preview and the validation containment check each re-derived "is a tower printed here" from the filament count instead of reading the estimate, so both missed the towers printed with no tool change to purge for. They now take the answer from the footprint, which is the drift this shared estimate exists to remove. A tower that is not printed estimates to zero, so its hull is degenerate and every check on it passes trivially - the containment check needs no gate of its own. WipeTowerData::width was written only by the pre-generation estimate and left at zero for the whole post-generation life of the Print, while its neighbour depth held the real value. Set it from the generator in both branches. The plate's height scan transformed every model part's full mesh per instance on each scene reload, discarding all but the z extent. The cached convex hull has the same z extent. A plate loaded from a sliced .gcode.3mf holds no objects and its filaments live in slice_filaments_info; the config-taking get_extruders overload returned an empty list for it, which sized the tower for a placeholder two filaments. It now answers the way the wx overload does, without reaching the plater. Also drop estimate_wipe_tower_size, which has no callers.
This commit is contained in:
@@ -45,48 +45,61 @@ static DynamicPrintConfig make_config(const char *wall_type = "rectangle")
|
||||
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);
|
||||
const WipeTowerFootprint fp = estimate_wipe_tower_footprint(config, 3, 0.2, 5.);
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 3, 0.1, 5.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(spaced, 3, 0.2, 5.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 100.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 50.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100.).depth, WithinAbs(0., 1e-9));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 0, 0.2, 100.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100.).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));
|
||||
// A raft is not one of them: normalize_fdm_2 clears enable_prime_tower for a plate that
|
||||
// purges one filament unless smooth timelapse or wrapping detection is on, so a raft
|
||||
// alone leaves no tower to reserve for.
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 100.).depth, WithinAbs(20., 1e-9));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, 5.).depth, WithinAbs(10., 1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("A tool change reserves the stability floor even with nothing to purge", "[WipeTowerEstimate]") {
|
||||
// The purge volumes are configurable down to zero, but the tool changes are still printed on
|
||||
// the tower and the generator still floors it, so the estimate has to floor it too.
|
||||
const double height = GENERATE(5., 100.);
|
||||
const float floor = WipeTower::get_limit_depth_by_height(float(height));
|
||||
DynamicPrintConfig config = make_config(GENERATE("rectangle", "rib"));
|
||||
config.set_key_value("prime_volume", new ConfigOptionFloat(0.));
|
||||
|
||||
CHECK(estimate_wipe_tower_footprint(config, 3, 0.2, height).depth >= floor);
|
||||
// Still nothing for a lone filament with no other reason.
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 1, 0.2, height).depth, WithinAbs(0., 1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("Both wall types agree on whether there is a tower at all", "[WipeTowerEstimate]") {
|
||||
@@ -97,34 +110,34 @@ TEST_CASE("Both wall types agree on whether there is a tower at all", "[WipeTowe
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(rect, 1, 0.2, height).depth, WithinAbs(0., 1e-9));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(rib, 1, 0.2, height).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(rect, 1, 0.2, height).depth, WithinAbs(0., 1e-9));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(rib, 1, 0.2, height).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)));
|
||||
CHECK(estimate_wipe_tower_footprint(rect, 2, 0.2, height).depth >= WipeTower::get_limit_depth_by_height(float(height)));
|
||||
CHECK(estimate_wipe_tower_footprint(rib, 2, 0.2, height).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);
|
||||
WipeTowerFootprint fp = estimate_wipe_tower_footprint(config, 3, 0.2, 5.);
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 3, 0.2, 5.).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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5.).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]") {
|
||||
@@ -144,7 +157,7 @@ TEST_CASE("Every wall and tower type is read the same from a preset and a static
|
||||
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);
|
||||
const WipeTowerFootprint fp = estimate_wipe_tower_footprint(preset, 3, 0.2, 5.);
|
||||
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));
|
||||
@@ -153,7 +166,7 @@ TEST_CASE("Every wall and tower type is read the same from a preset and a static
|
||||
CHECK_THAT(fp.depth, WithinAbs(20., 1e-9));
|
||||
}
|
||||
|
||||
const WipeTowerFootprint from_static = estimate_wipe_tower_footprint(static_config, 3, 0.2, 5., false);
|
||||
const WipeTowerFootprint from_static = estimate_wipe_tower_footprint(static_config, 3, 0.2, 5.);
|
||||
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));
|
||||
@@ -162,8 +175,8 @@ TEST_CASE("Every wall and tower type is read the same from a preset and a static
|
||||
// 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.);
|
||||
CHECK(estimate_wipe_tower_footprint(preset, 1, 0.2, 5.).depth > 0.);
|
||||
CHECK(estimate_wipe_tower_footprint(static_config, 1, 0.2, 5.).depth > 0.);
|
||||
}
|
||||
|
||||
TEST_CASE("A dual nozzle purges every filament plus the filament change", "[WipeTowerEstimate]") {
|
||||
@@ -173,7 +186,7 @@ TEST_CASE("A dual nozzle purges every filament plus the filament change", "[Wipe
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5.).depth, WithinAbs((200. + change_volume) / 10., 1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("The shipped defaults size the tower from the flush matrix", "[WipeTowerEstimate]") {
|
||||
@@ -188,7 +201,7 @@ TEST_CASE("The shipped defaults size the tower from the flush matrix", "[WipeTow
|
||||
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(config, 2, 0.2, 5.).depth, WithinAbs(expected, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A config missing a tower key falls back to that key's default", "[WipeTowerEstimate]") {
|
||||
@@ -201,6 +214,6 @@ TEST_CASE("A config missing a tower key falls back to that key's default", "[Wip
|
||||
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));
|
||||
CHECK_THAT(estimate_wipe_tower_footprint(partial, 3, 0.2, 5.).depth,
|
||||
WithinAbs(estimate_wipe_tower_footprint(defaulted, 3, 0.2, 5.).depth, 1e-9));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user