mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 18:56:13 +00:00
Verify WipeTower Footprint at Point of Generation
The clamps and validation work from estimates. Once the tower is generated, _make_wipe_tower re-tests the exact first-layer footprint, brim and cone base included, against the printable area and the exclusion zone, so an off-plate tower fails with a clear error instead of exporting unprintable G-code. The rectangle-wall mesh footprint learns the Type2 cone base so that check and the post-generation validation see the real outline. Pre-generation, validation hard-checks the body plus an explicit brim and warns on the estimated auto brim and cone base with the existing "may collide" strings, so the user hears about a marginal position on the first slice rather than only at generation time. Two fff_print fixtures that print a tower at the default position move it onto the 200 mm test bed, as the multifilament fixtures already do: the shipped default y of 220 is off that bed, and the backstop now says so instead of exporting the tower.
This commit is contained in:
@@ -1068,33 +1068,57 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
|||||||
convex_hulls_temp.push_back(wipe_tower_polygon);
|
convex_hulls_temp.push_back(wipe_tower_polygon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Post-generation the mesh bottom already carries the brim. Pre-generation the body grows
|
||||||
|
// by the brim only when its width is explicit; the auto brim and a Type2 cone base depend on
|
||||||
|
// the tower height, exact only once generated, so they only warn here - the exact footprint
|
||||||
|
// is re-checked in _make_wipe_tower.
|
||||||
|
const bool exact_footprint = print.is_step_done(psWipeTower);
|
||||||
|
Polygons tower_polys_checked = (!exact_footprint && config.prime_tower_brim_width.value >= 0) ?
|
||||||
|
offset(convex_hulls_temp, float(scale_(brim_width))) :
|
||||||
|
convex_hulls_temp;
|
||||||
|
Polygons tower_polys_estimated;
|
||||||
|
if (!exact_footprint && !convex_hulls_temp.empty()) {
|
||||||
|
Polygon base = convex_hulls_temp.front();
|
||||||
|
if (config.wipe_tower_wall_type.value == WipeTowerWallType::wtwCone && print.wipe_tower_type() == WipeTowerType::Type2) {
|
||||||
|
double max_height = 0.;
|
||||||
|
for (const PrintObject *object : print.objects())
|
||||||
|
max_height = std::max(max_height, unscale_(object->size().z()));
|
||||||
|
base = WipeTower2::cone_base_polygon(width, depth, max_height, config.wipe_tower_cone_angle.value);
|
||||||
|
base.rotate(Geometry::deg2rad(a));
|
||||||
|
base.translate(Point(scale_(x), scale_(y)));
|
||||||
|
}
|
||||||
|
tower_polys_estimated = offset(base, float(scale_(brim_width)));
|
||||||
|
}
|
||||||
|
// Object proximity stays a body-only warning: brim near-misses would newly warn on
|
||||||
|
// many setups that print fine.
|
||||||
if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) {
|
if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) {
|
||||||
if (warning) {
|
if (warning) {
|
||||||
warning->string += L("Prime Tower") + L(" is too close to others, and collisions may be caused.\n");
|
warning->string += L("Prime Tower") + L(" is too close to others, and collisions may be caused.\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!intersection(exclude_polys, convex_hulls_temp).empty()) {
|
if (!intersection(exclude_polys, tower_polys_checked).empty()) {
|
||||||
/*if (warning) {
|
|
||||||
warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n");
|
|
||||||
}*/
|
|
||||||
return {L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n")};
|
return {L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n")};
|
||||||
}
|
}
|
||||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, tower_polys_checked).empty()) {
|
||||||
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
||||||
}
|
}
|
||||||
// No gate on "is there a tower": one that is not printed estimates to zero, so the hull
|
if (warning && !intersection(exclude_polys, tower_polys_estimated).empty()) {
|
||||||
// is degenerate and every check passes. Re-deriving it here missed the wrapping-detection
|
warning->string += L("Prime Tower") + L(" is too close to exclusion area, there may be collisions when printing.") + "\n";
|
||||||
|
}
|
||||||
|
if (warning && print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, tower_polys_estimated).empty()) {
|
||||||
|
warning->string += L("Prime Tower") + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n";
|
||||||
|
}
|
||||||
|
// No gate on "is there a tower": one that is not printed estimates to zero, so the hulls
|
||||||
|
// are degenerate and every check passes. Re-deriving it here missed the wrapping-detection
|
||||||
// tower on a single-filament plate.
|
// tower on a single-filament plate.
|
||||||
// Pre-generation, grow the body by the brim to match what the generator draws;
|
|
||||||
// post-generation the mesh already includes it.
|
|
||||||
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
|
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
|
||||||
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
|
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
|
||||||
for (Polygon &p : printable_polys)
|
for (Polygon &p : printable_polys)
|
||||||
p.translate(plate_shift);
|
p.translate(plate_shift);
|
||||||
Polygons tower_polys_with_brim = print.is_step_done(psWipeTower) ?
|
if (!diff(tower_polys_checked, printable_polys).empty())
|
||||||
convex_hulls_temp : offset(convex_hulls_temp, float(scale_(brim_width)));
|
|
||||||
if (!diff(tower_polys_with_brim, printable_polys).empty())
|
|
||||||
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
|
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
|
||||||
|
if (warning && !diff(tower_polys_estimated, printable_polys).empty())
|
||||||
|
warning->string += L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4390,7 +4414,9 @@ void Print::_make_wipe_tower()
|
|||||||
wipe_tower.get_wipe_tower_height(), wipe_tower.get_brim_width(),
|
wipe_tower.get_wipe_tower_height(), wipe_tower.get_brim_width(),
|
||||||
config().wipe_tower_wall_type.value == WipeTowerWallType::wtwRib,
|
config().wipe_tower_wall_type.value == WipeTowerWallType::wtwRib,
|
||||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||||
config().wipe_tower_fillet_wall.value);
|
config().wipe_tower_fillet_wall.value,
|
||||||
|
config().wipe_tower_wall_type.value == WipeTowerWallType::wtwCone ?
|
||||||
|
(float) config().wipe_tower_cone_angle.value : 0.f);
|
||||||
const Vec3d origin = Vec3d::Zero();
|
const Vec3d origin = Vec3d::Zero();
|
||||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
||||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
||||||
@@ -4403,6 +4429,28 @@ void Print::_make_wipe_tower()
|
|||||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||||
{scale_(origin.x()), scale_(origin.y())});
|
{scale_(origin.x()), scale_(origin.y())});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The clamps and checks above work from estimates; re-test the exact generated footprint
|
||||||
|
// so an off-plate tower fails with a clear error instead of exporting unprintable G-code
|
||||||
|
// (validate() only sees the mesh on its next run).
|
||||||
|
if (m_wipe_tower_data.wipe_tower_mesh_data) {
|
||||||
|
Polygon footprint = m_wipe_tower_data.wipe_tower_mesh_data->bottom; // includes brim and rib offset
|
||||||
|
footprint.rotate(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value));
|
||||||
|
footprint.translate(Point(scale_(m_config.wipe_tower_x.get_at(m_plate_index)),
|
||||||
|
scale_(m_config.wipe_tower_y.get_at(m_plate_index))));
|
||||||
|
const Polygons printable_polys = this->get_extruder_shared_printable_polygon();
|
||||||
|
if (!printable_polys.empty() && !diff(Polygons{footprint}, printable_polys).empty()) {
|
||||||
|
const BoundingBox fp = get_extents(footprint);
|
||||||
|
const BoundingBox pr = get_extents(printable_polys);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("wipe tower footprint [%1%,%2%]-[%3%,%4%] leaves printable [%5%,%6%]-[%7%,%8%]") %
|
||||||
|
unscaled(fp.min.x()) % unscaled(fp.min.y()) % unscaled(fp.max.x()) % unscaled(fp.max.y()) %
|
||||||
|
unscaled(pr.min.x()) % unscaled(pr.min.y()) % unscaled(pr.max.x()) % unscaled(pr.max.y());
|
||||||
|
throw Slic3r::SlicingError(L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n"));
|
||||||
|
}
|
||||||
|
// The cutter/purge corner is a physical obstacle — the brim must stay out like the body.
|
||||||
|
if (!intersection(get_bed_excluded_area(m_config), Polygons{footprint}).empty())
|
||||||
|
throw Slic3r::SlicingError(L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
|
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
|
||||||
@@ -5951,12 +5999,21 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
|
|||||||
}
|
}
|
||||||
return wtels;
|
return wtels;
|
||||||
}
|
}
|
||||||
void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall)
|
void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall, float cone_angle)
|
||||||
{
|
{
|
||||||
wipe_tower_mesh_data = WipeTowerMeshData{};
|
wipe_tower_mesh_data = WipeTowerMeshData{};
|
||||||
float first_layer_height=0.08; //brim height
|
float first_layer_height=0.08; //brim height
|
||||||
if (width < EPSILON || depth < EPSILON || height < EPSILON) return;
|
if (width < EPSILON || depth < EPSILON || height < EPSILON) return;
|
||||||
if (!is_rib_wipe_tower || rib_length < EPSILON) {
|
if (cone_angle > EPSILON && (!is_rib_wipe_tower || rib_length < EPSILON)) {
|
||||||
|
// Cone tower: the base bulges past the body box; this bottom polygon feeds the
|
||||||
|
// containment checks, so it must carry the bulge and the brim (cone not lofted).
|
||||||
|
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
|
||||||
|
wipe_tower_mesh_data->bottom = WipeTower2::cone_base_polygon(width, depth, height, cone_angle);
|
||||||
|
auto brim_bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width));
|
||||||
|
if (!brim_bottom.empty())
|
||||||
|
wipe_tower_mesh_data->bottom = brim_bottom.front();
|
||||||
|
wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height);
|
||||||
|
} else if (!is_rib_wipe_tower || rib_length < EPSILON) {
|
||||||
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
|
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
|
||||||
wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height);
|
wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height);
|
||||||
wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0});
|
wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0});
|
||||||
|
|||||||
@@ -804,7 +804,7 @@ struct WipeTowerData
|
|||||||
rib_offset = Vec2f::Zero();
|
rib_offset = Vec2f::Zero();
|
||||||
wipe_tower_mesh_data = std::nullopt;
|
wipe_tower_mesh_data = std::nullopt;
|
||||||
}
|
}
|
||||||
void construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length, bool fillet_wall);
|
void construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length, bool fillet_wall, float cone_angle = 0.f);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Only allow the WipeTowerData to be instantiated internally by Print,
|
// Only allow the WipeTowerData to be instantiated internally by Print,
|
||||||
|
|||||||
@@ -574,6 +574,9 @@ static DynamicPrintConfig dual_extruder_toolchange_config()
|
|||||||
config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({240, 240}));
|
config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({240, 240}));
|
||||||
config.set_key_value("flush_multiplier", new ConfigOptionFloats({1}));
|
config.set_key_value("flush_multiplier", new ConfigOptionFloats({1}));
|
||||||
config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats({0, 140, 140, 0}));
|
config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats({0, 140, 140, 0}));
|
||||||
|
// Inside the 200x200 test bed; the default y, 220, is not, and generation rejects that.
|
||||||
|
config.set_key_value("wipe_tower_x", new ConfigOptionFloats({50.}));
|
||||||
|
config.set_key_value("wipe_tower_y", new ConfigOptionFloats({50.}));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ static DynamicPrintConfig wipe_tower_toolchange_config(const std::string &gcode_
|
|||||||
{ "outer_wall_filament_id", 2 },
|
{ "outer_wall_filament_id", 2 },
|
||||||
{ "inner_wall_filament_id", 2 },
|
{ "inner_wall_filament_id", 2 },
|
||||||
{ "enable_prime_tower", true },
|
{ "enable_prime_tower", true },
|
||||||
|
{ "wipe_tower_x", 50 }, // inside the 200x200 test bed
|
||||||
|
{ "wipe_tower_y", 50 }, // (the default y, 220, is not)
|
||||||
{ "layer_height", 0.3 },
|
{ "layer_height", 0.3 },
|
||||||
{ "gcode_flavor", gcode_flavor },
|
{ "gcode_flavor", gcode_flavor },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user