ENH: generate outer wall contour paths in arachne

1. Add OuterWallContourStrategy class to generate outer wall contour paths.
2. Fix top one wall issue in arachne

jira:NONE

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: I44574df765cdd0d0d3fc4f6c3f7b846dfb4fa21f
This commit is contained in:
xun.zhang 2025-01-02 16:13:36 +08:00 committed by lane.wei
parent 032b34eded
commit 6018d326f4
9 changed files with 256 additions and 57 deletions

View File

@ -13,6 +13,8 @@ namespace Slic3r::Arachne
template<typename T> constexpr T pi_div(const T div) { return static_cast<T>(M_PI) / div; }
constexpr int WallContourMarkedWidth = 0;
constexpr int FirstWallContourMarkedWidth = 1;
/*!
* Mostly virtual base class template.
*

View File

@ -8,6 +8,7 @@
#include "DistributedBeadingStrategy.hpp"
#include "RedistributeBeadingStrategy.hpp"
#include "OuterWallInsetBeadingStrategy.hpp"
#include "OuterWallContourStrategy.hpp"
#include <limits>
#include <boost/log/trivial.hpp>
@ -44,9 +45,16 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy(
ret = std::make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, std::move(ret));
}
//Apply the OuterWallContourStrategy last, since that adds a 1-width marker wall to mark the boundary of first beading.
BOOST_LOG_TRIVIAL(debug) << "Applying the First Beading Contour Strategy.";
ret = std::make_unique<OuterWallContourStrategy>(std::move(ret));
//Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch.
BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << ".";
ret = std::make_unique<LimitedBeadingStrategy>(max_bead_count, std::move(ret));
ret = std::make_unique<LimitedBeadingStrategy>(max_bead_count + 2, std::move(ret));
return ret;
}
} // namespace Slic3r::Arachne

View File

@ -48,7 +48,7 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes
const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, WallContourMarkedWidth);
}
return ret;
}
@ -77,14 +77,14 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes
coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, WallContourMarkedWidth);
//Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then.
const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1);
innermost_toolpath_location = ret.toolpath_locations[opposite_bead];
innermost_toolpath_width = ret.bead_widths[opposite_bead];
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2);
ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0);
ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, WallContourMarkedWidth);
return ret;
}

View File

@ -0,0 +1,83 @@
#include "OuterWallContourStrategy.hpp"
#include "Point.hpp"
namespace Slic3r::Arachne
{
OuterWallContourStrategy::OuterWallContourStrategy(BeadingStrategyPtr parent)
: BeadingStrategy(*parent)
, parent(std::move(parent))
{
}
std::string OuterWallContourStrategy::toString() const
{
return std::string("OuterWallContourStrategy+") + parent->toString();
}
coord_t OuterWallContourStrategy::getTransitioningLength(coord_t lower_bead_count) const
{
return parent->getTransitioningLength(lower_bead_count);
}
float OuterWallContourStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
{
return parent->getTransitionAnchorPos(lower_bead_count);
}
std::vector<coord_t> OuterWallContourStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
{
return parent->getNonlinearThicknesses(lower_bead_count);
}
coord_t OuterWallContourStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
if(lower_bead_count <= 1)
return parent->getTransitionThickness(lower_bead_count);
else if(lower_bead_count == 2 || lower_bead_count ==3)
return parent->getTransitionThickness(1);
return parent->getTransitionThickness(lower_bead_count-2);
}
coord_t OuterWallContourStrategy::getOptimalBeadCount(coord_t thickness) const
{
coord_t parent_bead_count = parent->getOptimalBeadCount(thickness);
if(parent_bead_count <= 1)
return parent_bead_count;
return parent_bead_count + 2;
}
coord_t OuterWallContourStrategy::getOptimalThickness(coord_t bead_count) const
{
if (bead_count <= 1)
return parent->getOptimalThickness(bead_count);
return parent->getOptimalThickness(bead_count - 2) + 2;
}
BeadingStrategy::Beading OuterWallContourStrategy::compute(coord_t thickness, coord_t bead_count) const
{
if (bead_count <= 1)
return parent->compute(thickness, bead_count);
assert(bead_count >= 3);
Beading ret = parent->compute(thickness, bead_count - 2);
if(ret.toolpath_locations.size() == 1){
return ret;
}
if(ret.toolpath_locations.size() > 0 ){
assert(ret.bead_widths.size()>0);
double location = ret.toolpath_locations.front() + ret.bead_widths.front() / 2;
double location_reverse = ret.toolpath_locations.back() - ret.bead_widths.back() / 2;
ret.toolpath_locations.insert(ret.toolpath_locations.begin()+1, location);
ret.bead_widths.insert(ret.bead_widths.begin()+1, FirstWallContourMarkedWidth);
ret.toolpath_locations.insert((ret.toolpath_locations.rbegin()+1).base(), location_reverse);
ret.bead_widths.insert((ret.bead_widths.rbegin()).base(), FirstWallContourMarkedWidth);
}
return ret;
}
} // namespace Slic3r::Arachne

View File

@ -0,0 +1,29 @@
#ifndef OUTER_WALL_CONTOUR_STRATEGY_H
#define OUTER_WALL_CONTOUR_STRATEGY_H
#include "BeadingStrategy.hpp"
namespace Slic3r::Arachne
{
class OuterWallContourStrategy : public BeadingStrategy
{
public:
OuterWallContourStrategy(BeadingStrategyPtr parent);
~OuterWallContourStrategy() override = default;
Beading compute(coord_t thickness, coord_t bead_count) const override;
coord_t getOptimalThickness(coord_t bead_count) const override;
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
coord_t getOptimalBeadCount(coord_t thickness) const override;
std::string toString() const override;
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const override;
protected:
const BeadingStrategyPtr parent;
};
}
#endif

View File

@ -700,27 +700,37 @@ const std::vector<VariableWidthLines> &WallToolPaths::getToolPaths()
void WallToolPaths::separateOutInnerContour()
{
enum PathType{
ActualPath,
WallContour,
FirstWallContour
};
//We'll remove all 0-width paths from the original toolpaths and store them separately as polygons.
std::vector<VariableWidthLines> actual_toolpaths;
actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude.
std::vector<VariableWidthLines> contour_paths;
contour_paths.reserve(toolpaths.size() / inset_count);
std::vector<VariableWidthLines> wall_contour_paths;
wall_contour_paths.reserve(toolpaths.size() / inset_count);
std::vector<VariableWidthLines> first_wall_contour_paths;
inner_contour.clear();
first_wall_contour.clear();
for (const VariableWidthLines &inset : toolpaths) {
if (inset.empty())
continue;
bool is_contour = false;
PathType type;
for (const ExtrusionLine &line : inset) {
for (const ExtrusionJunction &j : line) {
if (j.w == 0)
is_contour = true;
if (j.w == Arachne::WallContourMarkedWidth)
type = WallContour;
else if(j.w == Arachne::FirstWallContourMarkedWidth)
type = FirstWallContour;
else
is_contour = false;
type = ActualPath;
break;
}
}
if (is_contour) {
if (type==WallContour) {
#ifdef DEBUG
for (const ExtrusionLine &line : inset)
for (const ExtrusionJunction &j : line)
@ -732,7 +742,16 @@ void WallToolPaths::separateOutInnerContour()
else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon
inner_contour.emplace_back(line.toPolygon());
}
} else {
}
else if (type == FirstWallContour){
for (const ExtrusionLine &line : inset) {
if (line.is_odd)
continue;
else if (line.is_closed)
first_wall_contour.emplace_back(line.toPolygon());
}
}
else {
actual_toolpaths.emplace_back(inset);
}
}
@ -747,6 +766,15 @@ void WallToolPaths::separateOutInnerContour()
//This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon.
//The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation.
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
first_wall_contour = union_(first_wall_contour, ClipperLib::PolyFillType::pftEvenOdd);
// restore the idx of non first wall paths
for (auto& paths : toolpaths) {
for (auto& path : paths) {
if (path.inset_idx > 1)
path.inset_idx -= 1;
}
}
}
const Polygons& WallToolPaths::getInnerContour()
@ -762,6 +790,20 @@ const Polygons& WallToolPaths::getInnerContour()
return inner_contour;
}
const Polygons& WallToolPaths::getFirstWallContour()
{
if (!toolpaths_generated && inset_count > 0)
{
generate();
}
else if(inset_count == 0)
{
return {};
}
return first_wall_contour;
}
bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths)
{
toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines)

View File

@ -78,6 +78,8 @@ public:
*/
const Polygons& getInnerContour();
const Polygons& getFirstWallContour();
/*!
* Removes empty paths from the toolpaths
* \param toolpaths the VariableWidthPaths generated with \p generate()
@ -131,6 +133,7 @@ private:
bool toolpaths_generated; //<! Are the toolpaths generated
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
Polygons inner_contour; //<! The inner contour of the generated toolpaths
Polygons first_wall_contour; //<! The contour of the first wall
const WallToolPathsParams m_params;
private:
bool enable_hole_compensation{ false };

View File

@ -401,6 +401,8 @@ set(lisbslic3r_sources
Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
Arachne/BeadingStrategy/OuterWallContourStrategy.hpp
Arachne/BeadingStrategy/OuterWallContourStrategy.cpp
Arachne/utils/ExtrusionJunction.hpp
Arachne/utils/ExtrusionJunction.cpp
Arachne/utils/ExtrusionLine.hpp

View File

@ -1797,14 +1797,11 @@ void PerimeterGenerator::process_arachne()
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface& surface : this->slices->surfaces) {
bool generate_one_wall = false;
bool generate_one_wall_by_first_layer = this->object_config->only_one_wall_first_layer && layer_id == 0;
bool generate_one_wall_by_top_one_wall = this->object_config->top_one_wall_type == TopOneWallType::Topmost && this->upper_slices == nullptr ||
this->object_config->top_one_wall_type == TopOneWallType::Alltop;
generate_one_wall = generate_one_wall_by_first_layer || generate_one_wall_by_top_one_wall;
bool generate_one_wall = generate_one_wall_by_first_layer || generate_one_wall_by_top_one_wall;
// detect how many perimeters must be generated for this island
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
@ -1815,35 +1812,6 @@ void PerimeterGenerator::process_arachne()
if (last.size() != 1 || new_size != surface.expolygon.num_contours())
apply_circle_compensation = false;
std::vector<int> circle_poly_indices;
Polygons last_p;
if (apply_circle_compensation)
last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices);
else
last_p = to_polygons(last);
// check whether to activate one wall mode
if (generate_one_wall && !generate_one_wall_by_first_layer)
{
ExPolygons top_expolys;
ExPolygons infill_contour_by_one_wall = offset_ex(last, -(ext_perimeter_width + perimeter_spacing) / 2.f);
BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall);
infill_bbox.offset(EPSILON);
Polygons upper_polygons_clipped;
if (this->upper_slices)
upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox);
top_expolys = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped);
Polygons lower_polygons_clipped;
if (this->lower_slices)
lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox);
ExPolygons bottom_expolys = offset_ex(diff_ex(top_expolys, lower_polygons_clipped), std::max(ext_perimeter_spacing, perimeter_width));
top_expolys = diff_ex(top_expolys, bottom_expolys);
generate_one_wall = should_enable_top_one_wall(last, top_expolys);
}
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
Arachne::WallToolPathsParams input_params;
@ -1864,15 +1832,77 @@ void PerimeterGenerator::process_arachne()
input_params.wall_distribution_count = this->object_config->wall_distribution_count.value;
}
coord_t real_loop_number = generate_one_wall ? 1 : loop_number + 1;
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, real_loop_number, 0, layer_height, input_params);
std::vector<int> circle_poly_indices;
Polygons last_p;
if (apply_circle_compensation)
last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices);
else
last_p = to_polygons(last);
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, 0, layer_height, input_params);
if (apply_circle_compensation)
wallToolPaths.EnableHoleCompensation(true, circle_poly_indices);
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
ExPolygons top_expolys_by_one_wall;
ExPolygons infill_contour_by_one_wall = union_ex(wallToolPaths.getFirstWallContour());
// do detail check whether to enable one wall
if (generate_one_wall && !generate_one_wall_by_first_layer)
{
BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall);
infill_bbox.offset(EPSILON);
Polygons upper_polygons_clipped;
if (this->upper_slices)
upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox);
top_expolys_by_one_wall = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped);
Polygons lower_polygons_clipped;
if (this->lower_slices)
lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox);
ExPolygons bottom_expolys = diff_ex(top_expolys_by_one_wall, lower_polygons_clipped);
top_expolys_by_one_wall = diff_ex(top_expolys_by_one_wall, bottom_expolys);
generate_one_wall = should_enable_top_one_wall(last, top_expolys_by_one_wall);
if (generate_one_wall)
top_expolys_by_one_wall = offset_ex(top_expolys_by_one_wall, perimeter_width);
}
std::vector<Arachne::VariableWidthLines> total_perimeters;
ExPolygons infill_contour;
if (!generate_one_wall) {
total_perimeters = wallToolPaths.getToolPaths();
infill_contour = union_ex(wallToolPaths.getInnerContour());
}
else if(loop_number >0) {
last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall);
last_p = to_polygons(last);
Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, 0, layer_height, input_params);
auto old_perimeters = wallToolPaths.getToolPaths();
auto new_perimeters = paths_new.getToolPaths();
for (auto& perimeters : old_perimeters) {
if (std::find_if(perimeters.begin(), perimeters.end(), [](auto& item) {return item.inset_idx == 0; }) != perimeters.end()) {
total_perimeters.emplace_back();
for (auto& p : perimeters) {
if (p.inset_idx == 0)
total_perimeters.back().emplace_back(std::move(p));
}
}
}
for (auto& perimeters : new_perimeters) {
if (!perimeters.empty()) {
for (auto& p : perimeters){
p.inset_idx += 1;
}
total_perimeters.emplace_back(std::move(perimeters));
}
}
infill_contour = union_ex(union_ex(paths_new.getInnerContour()), top_expolys_by_one_wall);
infill_contour = intersection_ex(infill_contour, infill_contour_by_one_wall);
}
#ifdef ARACHNE_DEBUG
{
static int iRun = 0;
@ -1883,15 +1913,15 @@ void PerimeterGenerator::process_arachne()
// All closed ExtrusionLine should have the same the first and the last point.
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
// equal the first and the last point.
assert([&perimeters = std::as_const(perimeters)]() -> bool {
for (const Arachne::VariableWidthLines& perimeter : perimeters)
assert([&total_perimeters = std::as_const(total_perimeters)]() -> bool {
for (const Arachne::VariableWidthLines& perimeter : total_perimeters)
for (const Arachne::ExtrusionLine& el : perimeter)
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
return false;
return true;
}());
int start_perimeter = int(perimeters.size()) - 1;
int start_perimeter = int(total_perimeters.size()) - 1;
int end_perimeter = -1;
int direction = -1;
@ -1899,15 +1929,15 @@ void PerimeterGenerator::process_arachne()
this->object_config->wall_sequence == WallSequence::OuterInner || this->object_config->wall_sequence == WallSequence::InnerOuterInner;
if (is_outer_wall_first) {
start_perimeter = 0;
end_perimeter = int(perimeters.size());
end_perimeter = int(total_perimeters.size());
direction = 1;
}
std::vector<Arachne::ExtrusionLine*> all_extrusions;
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
if (perimeters[perimeter_idx].empty())
if (total_perimeters[perimeter_idx].empty())
continue;
for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx])
for (Arachne::ExtrusionLine& wall : total_perimeters[perimeter_idx])
all_extrusions.emplace_back(&wall);
}
@ -2069,7 +2099,7 @@ void PerimeterGenerator::process_arachne()
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty())
this->loops->append(extrusion_coll);
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
const coord_t spacing = (total_perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
// collapse too narrow infill areas
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));