Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions include/InsetOrderOptimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ class InsetOrderOptimizer
* Odd walls should always go after their enclosing wall polygons.
*
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
* \param inner_outer_inner Sandwich order: deeper walls (third and up) from the inside out, then the outer wall, then the second wall. Overrides outer_to_inner.
*/
static value_type getRegionOrder(const std::vector<ExtrusionLine>& input, const bool outer_to_inner);
static value_type getRegionOrder(const std::vector<ExtrusionLine>& input, const bool outer_to_inner, const bool inner_outer_inner = false);

/*!
* Get the order constraints of the insets when printing walls per inset.
Expand All @@ -94,8 +95,9 @@ class InsetOrderOptimizer
* Odd walls should always go after their enclosing wall polygons.
*
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
* \param inner_outer_inner Sandwich order: deeper walls (third and up) from the inside out, then the outer wall, then the second wall. Overrides outer_to_inner.
*/
static value_type getInsetOrder(const auto& input, const bool outer_to_inner);
static value_type getInsetOrder(const auto& input, const bool outer_to_inner, const bool inner_outer_inner = false);

/*! Get the start position of the planned paths, or nullopt if optimize() has not been called yet or the paths are empty */
std::optional<Point2LL> getStartPosition() const;
Expand Down
6 changes: 6 additions & 0 deletions include/settings/EnumSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ enum class InsetDirection
*/
OUTSIDE_IN,

/*!
* Sandwich order: the third wall and all deeper walls are printed first from the inside out, then the outermost wall, then the second wall.
* With only two walls this is outside in. Only applies when all walls use the same extruder.
*/
INNER_OUTER_INNER,

/*!
* If the innermost wall is a central wall, it is printed last. Otherwise
* prints the same as inside out.
Expand Down
56 changes: 48 additions & 8 deletions src/InsetOrderOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,23 @@ void InsetOrderOptimizer::optimize()
const auto pack_by_inset = ! settings_.get<bool>("optimize_wall_printing_order");
const auto inset_direction = settings_.get<InsetDirection>((layer_nr_ == 0) ? "initial_layer_inset_direction" : "inset_direction");

const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN;
const bool use_one_extruder = wall_0_extruder_nr_ == wall_x_extruder_nr_;
// Inner/outer/inner only makes sense when one extruder prints all walls; otherwise fall back to the default inside out order.
const bool inner_outer_inner = inset_direction == InsetDirection::INNER_OUTER_INNER && use_one_extruder;
const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN;
const bool current_extruder_is_wall_x = wall_x_extruder_nr_ == extruder_nr_;

const bool reverse = shouldReversePath(use_one_extruder, current_extruder_is_wall_x, outer_to_inner);
const bool use_shortest_for_inner_walls = outer_to_inner;
walls_to_be_added_ = getWallsToBeAdded(reverse, use_one_extruder);

const std::unordered_multimap<const ExtrusionLine*, const ExtrusionLine*> order
= pack_by_inset ? getInsetOrder(walls_to_be_added_, outer_to_inner) : getRegionOrder(walls_to_be_added_, outer_to_inner);
= pack_by_inset ? getInsetOrder(walls_to_be_added_, outer_to_inner, inner_outer_inner) : getRegionOrder(walls_to_be_added_, outer_to_inner, inner_outer_inner);

constexpr bool detect_loops = false;
constexpr Shape* combing_boundary = nullptr;
const auto group_outer_walls = settings_.get<bool>("group_outer_walls");
// Grouping assumes the outer walls are at the start or end of the order, which doesn't hold for inner/outer/inner.
const auto group_outer_walls = settings_.get<bool>("group_outer_walls") && ! inner_outer_inner;
// When we alternate walls, also alternate the direction at which the first wall starts in.
// On even layers we start with normal direction, on odd layers with inverted direction.
path_optimizer_ = std::make_shared<PathOrderOptimizer<const ExtrusionLine*>>(
Expand Down Expand Up @@ -326,7 +329,7 @@ std::optional<size_t> InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed
return closest_junction_idx + 1;
}

InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const std::vector<ExtrusionLine>& extrusion_lines, const bool outer_to_inner)
InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const std::vector<ExtrusionLine>& extrusion_lines, const bool outer_to_inner, const bool inner_outer_inner)
{
if (extrusion_lines.empty())
{
Expand Down Expand Up @@ -436,13 +439,33 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const std::v
for (const ExtrusionLine* outer_wall : outer_walls)
{
const std::function<void(const ExtrusionLine*, const ExtrusionLine*)> set_order_constraints
= [&order, &closest_outer_wall_line, &outer_wall, &outer_to_inner](const auto& current_line, const auto& parent_line)
= [&order, &closest_outer_wall_line, &min_depth, &outer_wall, &outer_to_inner, &inner_outer_inner](const auto& current_line, const auto& parent_line)
{
// if the closest
if (closest_outer_wall_line[current_line] == outer_wall && parent_line != nullptr)
{
if (inner_outer_inner)
{
// Wanted order: innermost wall ... wall 3, wall 2, wall 0 (outer), wall 1.
const unsigned int depth = min_depth[current_line];
if (depth == 1)
{
// Outer wall before the second wall.
order.insert(std::make_pair(parent_line, current_line));
}
else if (depth == 2)
{
// Third wall before the outer wall, which is not adjacent to it, so link to the outer wall directly.
order.insert(std::make_pair(current_line, outer_wall));
}
else
{
// Remaining walls go from the inside out.
order.insert(std::make_pair(current_line, parent_line));
}
}
// flip the key values if we want to print from inner to outer walls
if (outer_to_inner)
else if (outer_to_inner)
{
order.insert(std::make_pair(parent_line, current_line));
}
Expand Down Expand Up @@ -472,7 +495,7 @@ std::optional<Point2LL> InsetOrderOptimizer::getStartPosition() const
return vert_data.at(first_path.start_vertex_);
}

InsetOrderOptimizer::value_type InsetOrderOptimizer::getInsetOrder(const auto& input, const bool outer_to_inner)
InsetOrderOptimizer::value_type InsetOrderOptimizer::getInsetOrder(const auto& input, const bool outer_to_inner, const bool inner_outer_inner)
{
value_type order;

Expand Down Expand Up @@ -506,14 +529,31 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getInsetOrder(const auto& i
{
const ExtrusionLine* before = inner_line;
const ExtrusionLine* after = line;
if (outer_to_inner)
// Inner/outer/inner prints the innermost wall ... wall 3, wall 2, wall 0 (outer), wall 1.
// So the outer wall goes before wall 1, and wall 2 only gets linked to the outer wall (below).
if (inner_outer_inner && inset_idx == 1)
{
continue;
}
if (outer_to_inner || (inner_outer_inner && inset_idx == 0))
{
std::swap(before, after);
}
order.emplace(before, after);
}
}
}
if (inner_outer_inner && walls_by_inset.size() > 2)
{
// The third wall is not adjacent to the outer wall, so make it come before the outer wall directly.
for (const ExtrusionLine* outer_line : walls_by_inset[0])
{
for (const ExtrusionLine* third_line : walls_by_inset[2])
{
order.emplace(third_line, outer_line);
}
}
}
for (size_t inset_idx = 1; inset_idx < fillers_by_inset.size(); inset_idx++)
{
for (const ExtrusionLine* line : fillers_by_inset[inset_idx])
Expand Down
2 changes: 2 additions & 0 deletions src/settings/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,8 @@ InsetDirection Settings::get<InsetDirection>(const std::string& key) const
return InsetDirection::INSIDE_OUT;
case "outside_in"_sw:
return InsetDirection::OUTSIDE_IN;
case "inner_outer_inner"_sw:
return InsetDirection::INNER_OUTER_INNER;
case "plugin"_sw:
return InsetDirection::PLUGIN;
default:
Expand Down
51 changes: 51 additions & 0 deletions tests/WallsComputationTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

#include "WallsComputation.h" //Unit under test.

#include <set>
#include <unordered_set>
#include <utility>
#include <vector>

#include <range/v3/view/join.hpp>
#include <scripta/logger.h>
Expand Down Expand Up @@ -228,5 +231,53 @@ TEST_F(WallsComputationTest, WallToolPathsGetWeakOrder)
EXPECT_EQ(has_order_info.size(), n_paths) << "Every path should have order information.";
}

/*!
* Tests the order constraints of the inner/outer/inner wall ordering. The third wall and the ones deeper inside print
* first, from the inside out, then the outer wall, then the second wall.
*/
TEST_F(WallsComputationTest, WallOrderInnerOuterInner)
{
// For each number of walls: the pairs of (wall that has to be printed first, wall that has to follow it), by wall index
// where 0 is the outer wall.
const std::vector<std::pair<size_t, std::set<std::pair<size_t, size_t>>>> expected_order_per_wall_count = {
{ 2, { { 0, 1 } } },
{ 3, { { 2, 0 }, { 0, 1 } } },
{ 4, { { 3, 2 }, { 2, 0 }, { 0, 1 } } },
{ 5, { { 4, 3 }, { 3, 2 }, { 2, 0 }, { 0, 1 } } },
};

for (const auto& [wall_count, expected_order] : expected_order_per_wall_count)
{
settings.add("wall_line_count", std::to_string(wall_count));
SliceLayer layer;
layer.parts.emplace_back();
SliceLayerPart& part = layer.parts.back();
part.outline.push_back(square_shape);

walls_computation.generateWalls(&layer, SectionType::WALL);

std::vector<ExtrusionLine> all_paths;
for (auto& line : part.wall_toolpaths | ranges::views::join)
{
all_paths.emplace_back(line);
}
ASSERT_EQ(all_paths.size(), wall_count) << "A square has one closed wall per wall line.";

// The outside-in flag has no say when the inner/outer/inner order is requested.
for (const bool outer_to_inner : { false, true })
{
constexpr bool inner_outer_inner = true;
const auto order = InsetOrderOptimizer::getRegionOrder(all_paths, outer_to_inner, inner_outer_inner);

std::set<std::pair<size_t, size_t>> actual_order;
for (const auto& [first, second] : order)
{
actual_order.emplace(first->inset_idx_, second->inset_idx_);
}
EXPECT_EQ(actual_order, expected_order) << "Wrong wall order with " << wall_count << " walls (outer to inner flag: " << outer_to_inner << ").";
}
}
}

} // namespace cura
// NOLINTEND(*-magic-numbers)
Loading