mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 19:17:33 +00:00
Share the Estimated First-Layer Outline of the Wipe Tower (#15571)
The preview brim, the placement margin and the pre-generation validation warning each decided on their own whether the tower has a Type2 cone base, reading the wall type and cone angle three different ways. The preview's read cast the preset's enum to ConfigOptionEnum<T>, which a preset-shaped config never holds, so the cone base was never previewed. estimate_wipe_tower_first_layer_outline now answers that question once, beside the footprint estimate, from the config and the resolved planner; all three sites take the outline from it. The libslic3r case reads the outline off a preset-shaped config, where the old cast came back empty.
This commit is contained in:
@@ -37,6 +37,17 @@ WipeTowerType resolve_wipe_tower_type(const ConfigBase &config)
|
|||||||
return type != nullptr ? WipeTowerType(type->getInt()) : WipeTowerType::Type2;
|
return type != nullptr ? WipeTowerType(type->getInt()) : WipeTowerType::Type2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Polygon estimate_wipe_tower_first_layer_outline(const ConfigBase &config, WipeTowerType tower_type, double width, double depth, double height)
|
||||||
|
{
|
||||||
|
// Type1 ignores the cone option. The wall type is read by value: a preset-shaped config
|
||||||
|
// holds it as ConfigOptionEnumGeneric, which a cast to ConfigOptionEnum<T> cannot see.
|
||||||
|
const ConfigOption *wall_type = option_of(config, "wipe_tower_wall_type");
|
||||||
|
const ConfigOption *cone_angle = option_of(config, "wipe_tower_cone_angle");
|
||||||
|
const bool cone = tower_type == WipeTowerType::Type2 && wall_type != nullptr &&
|
||||||
|
wall_type->getInt() == int(WipeTowerWallType::wtwCone) && cone_angle != nullptr;
|
||||||
|
return WipeTower2::cone_base_polygon(width, depth, height, cone ? cone_angle->getFloat() : 0.);
|
||||||
|
}
|
||||||
|
|
||||||
WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeTowerType tower_type, const std::vector<unsigned int> &filament_ids, double layer_height, double max_object_height)
|
WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeTowerType tower_type, const std::vector<unsigned int> &filament_ids, double layer_height, double max_object_height)
|
||||||
{
|
{
|
||||||
WipeTowerFootprint footprint;
|
WipeTowerFootprint footprint;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../Polygon.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ConfigBase;
|
class ConfigBase;
|
||||||
@@ -23,6 +25,12 @@ struct WipeTowerFootprint
|
|||||||
// the GUI and CLI placement can resolve it without a Print.
|
// the GUI and CLI placement can resolve it without a Print.
|
||||||
WipeTowerType resolve_wipe_tower_type(const ConfigBase &config);
|
WipeTowerType resolve_wipe_tower_type(const ConfigBase &config);
|
||||||
|
|
||||||
|
// First-layer outline of an estimated tower in tower-local scaled coordinates, brim excluded:
|
||||||
|
// the body box, or for a Type2 cone wall the box unioned with the cone's base. The preview,
|
||||||
|
// the placement margin and validation all take the outline from here so they cannot disagree
|
||||||
|
// about whether a cone exists.
|
||||||
|
Polygon estimate_wipe_tower_first_layer_outline(const ConfigBase &config, WipeTowerType tower_type, double width, double depth, double height);
|
||||||
|
|
||||||
// filament_ids: 0-based filaments purged on the plate. The config cannot see custom G-code tool
|
// filament_ids: 0-based filaments purged on the plate. The config cannot see custom G-code tool
|
||||||
// changes, so ids derived from the model must include them
|
// changes, so ids derived from the model must include them
|
||||||
// (Print::extruders(true)) or a real tower is sized as if it were never built.
|
// (Print::extruders(true)) or a real tower is sized as if it were never built.
|
||||||
|
|||||||
@@ -1078,15 +1078,12 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
|||||||
convex_hulls_temp;
|
convex_hulls_temp;
|
||||||
Polygons tower_polys_estimated;
|
Polygons tower_polys_estimated;
|
||||||
if (!exact_footprint && !convex_hulls_temp.empty()) {
|
if (!exact_footprint && !convex_hulls_temp.empty()) {
|
||||||
Polygon base = convex_hulls_temp.front();
|
double max_height = 0.;
|
||||||
if (config.wipe_tower_wall_type.value == WipeTowerWallType::wtwCone && print.wipe_tower_type() == WipeTowerType::Type2) {
|
for (const PrintObject *object : print.objects())
|
||||||
double max_height = 0.;
|
max_height = std::max(max_height, unscale_(object->size().z()));
|
||||||
for (const PrintObject *object : print.objects())
|
Polygon base = estimate_wipe_tower_first_layer_outline(config, print.wipe_tower_type(), width, depth, max_height);
|
||||||
max_height = std::max(max_height, unscale_(object->size().z()));
|
base.rotate(Geometry::deg2rad(a));
|
||||||
base = WipeTower2::cone_base_polygon(width, depth, max_height, config.wipe_tower_cone_angle.value);
|
base.translate(Point(scale_(x), scale_(y)));
|
||||||
base.rotate(Geometry::deg2rad(a));
|
|
||||||
base.translate(Point(scale_(x), scale_(y)));
|
|
||||||
}
|
|
||||||
tower_polys_estimated = offset(base, float(scale_(brim_width)));
|
tower_polys_estimated = offset(base, float(scale_(brim_width)));
|
||||||
}
|
}
|
||||||
// Object proximity stays a body-only warning: brim near-misses would newly warn on
|
// Object proximity stays a body-only warning: brim near-misses would newly warn on
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower.hpp"
|
#include "libslic3r/GCode/WipeTower.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower2.hpp"
|
|
||||||
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
#include "libslic3r/Tesselate.hpp"
|
#include "libslic3r/Tesselate.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
@@ -928,25 +927,13 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
|||||||
const float brim_height = 0.2f; // one first layer, visual only
|
const float brim_height = 0.2f; // one first layer, visual only
|
||||||
TriangleMesh brim_slab;
|
TriangleMesh brim_slab;
|
||||||
if (show_brim) {
|
if (show_brim) {
|
||||||
// A Type2 cone-wall tower's base bulges past the body box — follow the real base
|
// The brim follows the real first-layer outline: a Type2 cone-wall tower's base bulges
|
||||||
// outline instead of the rectangle. Type1 ignores the cone option.
|
// past the body box. The wall type and angle are print settings, the planner a printer one.
|
||||||
Polygon cone_base;
|
const DynamicPrintConfig &print_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
{
|
const DynamicPrintConfig &printer_cfg = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
// Preset enums are ConfigOptionEnumGeneric, so read them by value; the planner is
|
const Polygon outline = estimate_wipe_tower_first_layer_outline(print_cfg, resolve_wipe_tower_type(printer_cfg), width, depth, height);
|
||||||
// resolved as the estimate resolves it, off the printer preset.
|
const Polygons brim_outline = offset(outline, scaled(brim_width));
|
||||||
const DynamicPrintConfig &print_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
brim_slab = WipeTower::its_make_rib_brim(brim_outline.empty() ? outline : brim_outline.front(), brim_height);
|
||||||
const DynamicPrintConfig &printer_cfg = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
|
||||||
const ConfigOption *wall_opt = print_cfg.option("wipe_tower_wall_type");
|
|
||||||
if (wall_opt != nullptr && wall_opt->getInt() == int(WipeTowerWallType::wtwCone) && resolve_wipe_tower_type(printer_cfg) == WipeTowerType::Type2)
|
|
||||||
cone_base = WipeTower2::cone_base_polygon(width, depth, height, print_cfg.opt_float("wipe_tower_cone_angle"));
|
|
||||||
}
|
|
||||||
if (!cone_base.empty()) {
|
|
||||||
Polygons brim_outline = offset(cone_base, scaled(brim_width));
|
|
||||||
brim_slab = WipeTower::its_make_rib_brim(brim_outline.empty() ? cone_base : brim_outline.front(), brim_height);
|
|
||||||
} else {
|
|
||||||
brim_slab = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, brim_height);
|
|
||||||
brim_slab.translate({-brim_width, -brim_width, 0.f});
|
|
||||||
}
|
|
||||||
wipe_tower_shell.merge(brim_slab);
|
wipe_tower_shell.merge(brim_slab);
|
||||||
}
|
}
|
||||||
for (int extruder_id : plate_extruders) {
|
for (int extruder_id : plate_extruders) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower2.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"
|
||||||
@@ -2398,14 +2397,9 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
|||||||
// clamp put the brim off the bed. Matches set_default_wipe_tower_pos_for_plate.
|
// clamp put the brim off the bed. Matches set_default_wipe_tower_pos_for_plate.
|
||||||
float wp_brim_width = float(footprint.brim_width);
|
float wp_brim_width = float(footprint.brim_width);
|
||||||
// A Type2 stabilization cone bulges past the body box like a brim does - fold its worst-axis
|
// A Type2 stabilization cone bulges past the body box like a brim does - fold its worst-axis
|
||||||
// bulge into the same margin (Type1 ignores the cone option).
|
// bulge into the same margin.
|
||||||
const auto *cone_wall_opt = config.option("wipe_tower_wall_type");
|
const BoundingBox outline = get_extents(estimate_wipe_tower_first_layer_outline(config, resolve_wipe_tower_type(config), w, depth, wt_size.z()));
|
||||||
const auto *cone_angle_opt = config.option("wipe_tower_cone_angle");
|
wp_brim_width += float(std::max({0., unscaled(outline.max.x()) - w, unscaled(outline.max.y()) - depth, -unscaled(outline.min.x()), -unscaled(outline.min.y())}));
|
||||||
if (cone_wall_opt != nullptr && cone_wall_opt->getInt() == int(WipeTowerWallType::wtwCone) && cone_angle_opt != nullptr &&
|
|
||||||
cone_angle_opt->getFloat() > EPSILON && resolve_wipe_tower_type(config) == WipeTowerType::Type2) {
|
|
||||||
const BoundingBox cb = get_extents(WipeTower2::cone_base_polygon(w, depth, wt_size.z(), cone_angle_opt->getFloat()));
|
|
||||||
wp_brim_width += float(std::max({0., unscaled(cb.max.x()) - w, unscaled(cb.max.y()) - depth, -unscaled(cb.min.x()), -unscaled(cb.min.y())}));
|
|
||||||
}
|
|
||||||
// A position valid by WIPE_TOWER_MARGIN is the user's choice and stays untouched; an
|
// A position valid by WIPE_TOWER_MARGIN is the user's choice and stays untouched; an
|
||||||
// invalid one is re-placed with the comfort margin (falling back to the validity bounds
|
// invalid one is re-placed with the comfort margin (falling back to the validity bounds
|
||||||
// on cramped plates). std::clamp is UB if lo > hi, so keep every hi >= lo.
|
// on cramped plates). std::clamp is UB if lo > hi, so keep every hi >= lo.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower.hpp"
|
#include "libslic3r/GCode/WipeTower.hpp"
|
||||||
#include "libslic3r/GCode/WipeTower2.hpp"
|
#include "libslic3r/GCode/WipeTower2.hpp"
|
||||||
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||||
@@ -272,6 +274,33 @@ TEST_CASE("Every wall and tower type is read the same from a preset and a static
|
|||||||
CHECK(estimate(static_config, 1, 0.2, 5., type).depth > 0.);
|
CHECK(estimate(static_config, 1, 0.2, 5., type).depth > 0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The first-layer outline bulges only for a Type2 cone wall", "[WipeTowerEstimate]") {
|
||||||
|
// Read off a preset-shaped config, whose enums are ConfigOptionEnumGeneric: a cast to
|
||||||
|
// ConfigOptionEnum<T> sees no wall type there and would never find the cone.
|
||||||
|
DynamicPrintConfig config = make_config("cone");
|
||||||
|
config.set_key_value("wipe_tower_cone_angle", new ConfigOptionFloat(25.));
|
||||||
|
REQUIRE(dynamic_cast<const ConfigOptionEnumGeneric *>(config.option("wipe_tower_wall_type")) != nullptr);
|
||||||
|
const Polygon box = Polygon::new_scale({{0., 0.}, {35., 0.}, {35., 20.}, {0., 20.}});
|
||||||
|
auto is_box = [&box](const Polygon &outline) { return diff(Polygons{outline}, Polygons{box}).empty(); };
|
||||||
|
|
||||||
|
// A 25-degree cone on a 100 mm tower has a 22 mm base radius, past the 10 mm half-depth.
|
||||||
|
const Polygon cone = estimate_wipe_tower_first_layer_outline(config, WipeTowerType::Type2, 35., 20., 100.);
|
||||||
|
CHECK(unscaled(get_extents(cone).max.y()) > 20. + 1.);
|
||||||
|
CHECK(diff(Polygons{box}, Polygons{cone}).empty());
|
||||||
|
// Type1 ignores the cone option, and the other wall types have no cone.
|
||||||
|
CHECK(is_box(estimate_wipe_tower_first_layer_outline(config, WipeTowerType::Type1, 35., 20., 100.)));
|
||||||
|
for (const char *wall_type : {"rectangle", "rib"}) {
|
||||||
|
config.set_deserialize_strict("wipe_tower_wall_type", wall_type);
|
||||||
|
CHECK(is_box(estimate_wipe_tower_first_layer_outline(config, WipeTowerType::Type2, 35., 20., 100.)));
|
||||||
|
}
|
||||||
|
// The static config Print holds gives the same outline.
|
||||||
|
config.set_deserialize_strict("wipe_tower_wall_type", "cone");
|
||||||
|
FullPrintConfig static_config;
|
||||||
|
static_config.apply(config, true);
|
||||||
|
const Polygon from_static = estimate_wipe_tower_first_layer_outline(static_config, WipeTowerType::Type2, 35., 20., 100.);
|
||||||
|
CHECK(from_static.points == cone.points);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("A Bambu Lab printer always gets the Type1 planner", "[WipeTowerEstimate]") {
|
TEST_CASE("A Bambu Lab printer always gets the Type1 planner", "[WipeTowerEstimate]") {
|
||||||
DynamicPrintConfig config = make_config();
|
DynamicPrintConfig config = make_config();
|
||||||
config.set_deserialize_strict("wipe_tower_type", "type2");
|
config.set_deserialize_strict("wipe_tower_type", "type2");
|
||||||
|
|||||||
Reference in New Issue
Block a user