Updated Arachne with Cura master.

This commit is contained in:
Lukáš Hejl 2022-03-31 14:29:02 +02:00
parent e99b579f93
commit 3610afd393
40 changed files with 1219 additions and 1170 deletions

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <cassert>
@ -9,20 +9,29 @@
namespace Slic3r::Arachne
{
BeadingStrategy::BeadingStrategy(coord_t optimal_width, coord_t default_transition_length, float transitioning_angle)
BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle)
: optimal_width(optimal_width)
, wall_split_middle_threshold(wall_split_middle_threshold)
, wall_add_middle_threshold(wall_add_middle_threshold)
, default_transition_length(default_transition_length)
, transitioning_angle(transitioning_angle)
{
name = "Unknown";
}
BeadingStrategy::BeadingStrategy(const BeadingStrategy &other)
: optimal_width(other.optimal_width)
, wall_split_middle_threshold(other.wall_split_middle_threshold)
, wall_add_middle_threshold(other.wall_add_middle_threshold)
, default_transition_length(other.default_transition_length)
, transitioning_angle(other.transitioning_angle)
, name(other.name)
{}
coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
{
if (lower_bead_count == 0)
{
return scaled<coord_t>(0.01);
}
return default_transition_length;
}
@ -36,7 +45,7 @@ float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
std::vector<coord_t> BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
{
return std::vector<coord_t>();
return {};
}
std::string BeadingStrategy::toString() const
@ -44,14 +53,9 @@ std::string BeadingStrategy::toString() const
return name;
}
coord_t BeadingStrategy::getDefaultTransitionLength() const
double BeadingStrategy::getSplitMiddleThreshold() const
{
return default_transition_length;
}
coord_t BeadingStrategy::getOptimalWidth() const
{
return optimal_width;
return wall_split_middle_threshold;
}
double BeadingStrategy::getTransitioningAngle() const
@ -59,4 +63,17 @@ double BeadingStrategy::getTransitioningAngle() const
return transitioning_angle;
}
coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
return optimal_width * bead_count;
}
coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count);
const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1);
const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold;
return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width);
}
} // namespace Slic3r::Arachne

View file

@ -1,4 +1,4 @@
// Copyright (c) 2021 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef BEADING_STRATEGY_H
@ -15,11 +15,11 @@ template<typename T> constexpr T pi_div(const T div) { return static_cast<T>(M_P
/*!
* Mostly virtual base class template.
*
*
* Strategy for covering a given (constant) horizontal model thickness with a number of beads.
*
*
* The beads may have different widths.
*
*
* TODO: extend with printing order?
*/
class BeadingStrategy
@ -36,15 +36,17 @@ public:
coord_t left_over; //! The distance not covered by any bead; gap area.
};
BeadingStrategy(coord_t optimal_width, coord_t default_transition_length, float transitioning_angle = pi_div(3));
BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3));
virtual ~BeadingStrategy() {}
BeadingStrategy(const BeadingStrategy &other);
virtual ~BeadingStrategy() = default;
/*!
* Retrieve the bead widths with which to cover a given thickness.
*
*
* Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness.
*
*
* \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count
*/
virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0;
@ -52,12 +54,12 @@ public:
/*!
* The ideal thickness for a given \param bead_count
*/
virtual coord_t getOptimalThickness(coord_t bead_count) const = 0;
virtual coord_t getOptimalThickness(coord_t bead_count) const;
/*!
* The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1
*/
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const = 0;
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const;
/*!
* The number of beads should we ideally usefor a given model thickness
@ -66,14 +68,14 @@ public:
/*!
* The length of the transitioning region along the marked / significant regions of the skeleton.
*
*
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length.
*/
virtual coord_t getTransitioningLength(coord_t lower_bead_count) const;
/*!
* The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps.
*
*
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location.
*/
virtual float getTransitionAnchorPos(coord_t lower_bead_count) const;
@ -81,15 +83,14 @@ public:
/*!
* Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths.
* Ordered from lower thickness to higher.
*
*
* This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends.
*/
virtual std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const;
virtual std::string toString() const;
coord_t getOptimalWidth() const;
coord_t getDefaultTransitionLength() const;
double getSplitMiddleThreshold() const;
double getTransitioningAngle() const;
protected:
@ -97,6 +98,10 @@ protected:
coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances.
double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width.
double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width.
coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts
/*!

View file

@ -1,10 +1,9 @@
//Copyright (c) 2021 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "BeadingStrategyFactory.hpp"
#include "LimitedBeadingStrategy.hpp"
#include "CenterDeviationBeadingStrategy.hpp"
#include "WideningBeadingStrategy.hpp"
#include "DistributedBeadingStrategy.hpp"
#include "RedistributeBeadingStrategy.hpp"
@ -16,28 +15,7 @@
namespace Slic3r::Arachne
{
coord_t getWeightedAverage(const coord_t preferred_bead_width_outer, const coord_t preferred_bead_width_inner, const coord_t max_bead_count)
{
if(max_bead_count > preferred_bead_width_outer - preferred_bead_width_inner)
{
//The difference between outer and inner bead width would be spread out across so many lines that rounding errors would destroy the difference.
//Also catches the case of max_bead_count being "infinite" (max integer).
return (preferred_bead_width_outer + preferred_bead_width_inner) / 2;
}
if (max_bead_count > 2)
{
return ((preferred_bead_width_outer * 2) + preferred_bead_width_inner * (max_bead_count - 2)) / max_bead_count;
}
if (max_bead_count <= 0)
{
return preferred_bead_width_inner;
}
return preferred_bead_width_outer;
}
BeadingStrategyPtr BeadingStrategyFactory::makeStrategy
(
const BeadingStrategyType type,
BeadingStrategyPtr BeadingStrategyFactory::makeStrategy(
const coord_t preferred_bead_width_outer,
const coord_t preferred_bead_width_inner,
const coord_t preferred_transition_length,
@ -50,48 +28,25 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy
const coord_t max_bead_count,
const coord_t outer_wall_offset,
const int inward_distributed_center_wall_count,
const double minimum_variable_line_width
const double minimum_variable_line_ratio
)
{
using std::make_unique;
using std::move;
const coord_t bar_preferred_wall_width = getWeightedAverage(preferred_bead_width_outer, preferred_bead_width_inner, max_bead_count);
BeadingStrategyPtr ret;
switch (type)
{
case BeadingStrategyType::Center:
ret = make_unique<CenterDeviationBeadingStrategy>(bar_preferred_wall_width, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold);
break;
case BeadingStrategyType::Distributed:
ret = make_unique<DistributedBeadingStrategy>(bar_preferred_wall_width, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, std::numeric_limits<int>::max());
break;
case BeadingStrategyType::InwardDistributed:
ret = make_unique<DistributedBeadingStrategy>(bar_preferred_wall_width, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count);
break;
default:
BOOST_LOG_TRIVIAL(error) << "Cannot make strategy!";
return nullptr;
}
if(print_thin_walls)
{
BeadingStrategyPtr ret = std::make_unique<DistributedBeadingStrategy>(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count);
BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << ".";
ret = std::make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret));
if (print_thin_walls) {
BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << ".";
ret = make_unique<WideningBeadingStrategy>(move(ret), min_feature_size, min_bead_width);
ret = std::make_unique<WideningBeadingStrategy>(std::move(ret), min_feature_size, min_bead_width);
}
if (max_bead_count > 0)
{
BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner;
ret = make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, preferred_bead_width_inner, minimum_variable_line_width, 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 = make_unique<LimitedBeadingStrategy>(max_bead_count, move(ret));
}
if (outer_wall_offset > 0)
{
if (outer_wall_offset > 0) {
BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << ".";
ret = make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, move(ret));
ret = std::make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, 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));
return ret;
}
} // namespace Slic3r::Arachne

View file

@ -1,4 +1,4 @@
// Copyright (c) 2021 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef BEADING_STRATEGY_FACTORY_H
@ -15,20 +15,19 @@ class BeadingStrategyFactory
public:
static BeadingStrategyPtr makeStrategy
(
const BeadingStrategyType type,
const coord_t preferred_bead_width_outer = scaled<coord_t>(0.0005),
const coord_t preferred_bead_width_inner = scaled<coord_t>(0.0005),
const coord_t preferred_transition_length = scaled<coord_t>(0.0004),
const float transitioning_angle = M_PI / 4.0,
const bool print_thin_walls = false,
const coord_t min_bead_width = 0,
const coord_t min_feature_size = 0,
const double wall_split_middle_threshold = 0.5,
const double wall_add_middle_threshold = 0.5,
const coord_t max_bead_count = 0,
const coord_t outer_wall_offset = 0,
const int inward_distributed_center_wall_count = 2,
const double minimum_variable_line_width = 0.5
coord_t preferred_bead_width_outer = scaled<coord_t>(0.0005),
coord_t preferred_bead_width_inner = scaled<coord_t>(0.0005),
coord_t preferred_transition_length = scaled<coord_t>(0.0004),
float transitioning_angle = M_PI / 4.0,
bool print_thin_walls = false,
coord_t min_bead_width = 0,
coord_t min_feature_size = 0,
double wall_split_middle_threshold = 0.5,
double wall_add_middle_threshold = 0.5,
coord_t max_bead_count = 0,
coord_t outer_wall_offset = 0,
int inward_distributed_center_wall_count = 2,
double minimum_variable_line_width = 0.5
);
};

View file

@ -1,88 +0,0 @@
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include <algorithm>
#include "CenterDeviationBeadingStrategy.hpp"
namespace Slic3r::Arachne
{
CenterDeviationBeadingStrategy::CenterDeviationBeadingStrategy(const coord_t pref_bead_width,
const double transitioning_angle,
const double wall_split_middle_threshold,
const double wall_add_middle_threshold)
: BeadingStrategy(pref_bead_width, pref_bead_width / 2, transitioning_angle),
minimum_line_width_split(pref_bead_width * wall_split_middle_threshold),
minimum_line_width_add(pref_bead_width * wall_add_middle_threshold)
{
name = "CenterDeviationBeadingStrategy";
}
CenterDeviationBeadingStrategy::Beading CenterDeviationBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
{
Beading ret;
ret.total_thickness = thickness;
if (bead_count > 0)
{
// Set the bead widths
ret.bead_widths = std::vector<coord_t>(static_cast<size_t>(bead_count), optimal_width);
const coord_t optimal_thickness = getOptimalThickness(bead_count);
const coord_t diff_thickness = thickness - optimal_thickness; //Amount of deviation. Either spread out over the middle 2 lines, or concentrated in the center line.
const size_t center_bead_idx = ret.bead_widths.size() / 2;
if (bead_count % 2 == 0) // Even lines
{
const coord_t inner_bead_widths = optimal_width + diff_thickness / 2;
if (inner_bead_widths < minimum_line_width_add)
{
return compute(thickness, bead_count - 1);
}
ret.bead_widths[center_bead_idx - 1] = inner_bead_widths;
ret.bead_widths[center_bead_idx] = inner_bead_widths;
}
else // Uneven lines
{
const coord_t inner_bead_widths = optimal_width + diff_thickness;
if (inner_bead_widths < minimum_line_width_split)
{
return compute(thickness, bead_count - 1);
}
ret.bead_widths[center_bead_idx] = inner_bead_widths;
}
// Set the center line location of the bead toolpaths.
ret.toolpath_locations.resize(ret.bead_widths.size());
ret.toolpath_locations.front() = ret.bead_widths.front() / 2;
for (size_t bead_idx = 1; bead_idx < ret.bead_widths.size(); ++bead_idx)
{
ret.toolpath_locations[bead_idx] =
ret.toolpath_locations[bead_idx - 1] + (ret.bead_widths[bead_idx] + ret.bead_widths[bead_idx - 1]) / 2;
}
ret.left_over = 0;
}
else
{
ret.left_over = thickness;
}
return ret;
}
coord_t CenterDeviationBeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
return bead_count * optimal_width;
}
coord_t CenterDeviationBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
return lower_bead_count * optimal_width + (lower_bead_count % 2 == 1 ? minimum_line_width_split : minimum_line_width_add);
}
coord_t CenterDeviationBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
{
const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure.
const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines.
const coord_t minimum_line_width = naive_count % 2 == 1 ? minimum_line_width_split : minimum_line_width_add;
return naive_count + (remainder > minimum_line_width); // If there's enough space, fit an extra one.
}
} // namespace Slic3r::Arachne

View file

@ -1,42 +0,0 @@
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef CENTER_DEVIATION_BEADING_STRATEGY_H
#define CENTER_DEVIATION_BEADING_STRATEGY_H
#include "BeadingStrategy.hpp"
namespace Slic3r::Arachne
{
/*!
* This beading strategy makes the deviation in the thickness of the part
* entirely compensated by the innermost wall.
*
* The outermost walls all use the ideal width, as far as possible.
*/
class CenterDeviationBeadingStrategy : public BeadingStrategy
{
private:
// For uneven numbers of lines: Minimum line width for which the middle line will be split into two lines.
coord_t minimum_line_width_split;
// For even numbers of lines: Minimum line width for which a new middle line will be added between the two innermost lines.
coord_t minimum_line_width_add;
public:
CenterDeviationBeadingStrategy(coord_t pref_bead_width,
double transitioning_angle,
double wall_split_middle_threshold,
double wall_add_middle_threshold);
~CenterDeviationBeadingStrategy() override{};
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;
};
} // namespace Slic3r::Arachne
#endif // CENTER_DEVIATION_BEADING_STRATEGY_H

View file

@ -1,4 +1,4 @@
// Copyright (c) 2021 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include <numeric>
#include "DistributedBeadingStrategy.hpp"
@ -7,23 +7,17 @@ namespace Slic3r::Arachne
{
DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width,
const coord_t default_transition_length,
const double transitioning_angle,
const double wall_split_middle_threshold,
const double wall_add_middle_threshold,
const int distribution_radius)
: BeadingStrategy(optimal_width, default_transition_length, transitioning_angle)
, wall_split_middle_threshold(wall_split_middle_threshold)
, wall_add_middle_threshold(wall_add_middle_threshold)
const coord_t default_transition_length,
const double transitioning_angle,
const double wall_split_middle_threshold,
const double wall_add_middle_threshold,
const int distribution_radius)
: BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle)
{
if(distribution_radius >= 2)
{
one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1);
}
else
{
one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1;
}
name = "DistributedBeadingStrategy";
}
@ -32,13 +26,11 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
Beading ret;
ret.total_thickness = thickness;
if (bead_count > 2)
{
if (bead_count > 2) {
const coord_t to_be_divided = thickness - bead_count * optimal_width;
const float middle = static_cast<float>(bead_count - 1) / 2;
const auto getWeight = [middle, this](coord_t bead_idx)
{
const auto getWeight = [middle, this](coord_t bead_idx) {
const float dev_from_middle = bead_idx - middle;
return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle);
};
@ -46,65 +38,45 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
std::vector<float> weights;
weights.resize(bead_count);
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
{
weights[bead_idx] = getWeight(bead_idx);
}
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
{
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
const float weight_fraction = weights[bead_idx] / total_weight;
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
const coord_t width = optimal_width + splitup_left_over_weight;
if (bead_idx == 0)
{
ret.toolpath_locations.emplace_back(width / 2);
}
else
{
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
}
ret.bead_widths.emplace_back(width);
}
ret.left_over = 0;
}
else if (bead_count == 2)
{
} else if (bead_count == 2) {
const coord_t outer_width = thickness / 2;
ret.bead_widths.emplace_back(outer_width);
ret.bead_widths.emplace_back(outer_width);
ret.toolpath_locations.emplace_back(outer_width / 2);
ret.toolpath_locations.emplace_back(thickness - outer_width / 2);
ret.left_over = 0;
}
else if (bead_count == 1)
{
} else if (bead_count == 1) {
const coord_t outer_width = thickness;
ret.bead_widths.emplace_back(outer_width);
ret.toolpath_locations.emplace_back(outer_width / 2);
ret.left_over = 0;
}
else
{
} else {
ret.left_over = thickness;
}
return ret;
}
coord_t DistributedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
return bead_count * optimal_width;
}
coord_t DistributedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
return lower_bead_count * optimal_width + optimal_width * (lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold);
}
coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
{
return (thickness + optimal_width / 2) / optimal_width;
const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure.
const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines.
const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold);
return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one.
}
} // namespace Slic3r::Arachne

View file

@ -1,4 +1,4 @@
// Copyright (c) 2021 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef DISTRIBUTED_BEADING_STRATEGY_H
@ -17,30 +17,22 @@ namespace Slic3r::Arachne
class DistributedBeadingStrategy : public BeadingStrategy
{
protected:
// For uneven numbers of lines: Minimum factor of the optimal width for which the middle line will be split into two lines.
double wall_split_middle_threshold;
// For even numbers of lines: Minimum factor of the optimal width for which a new middle line will be added between the two innermost lines.
double wall_add_middle_threshold;
float one_over_distribution_radius_squared; // (1 / distribution_radius)^2
public:
/*!
* \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness
*/
DistributedBeadingStrategy( const coord_t optimal_width,
const coord_t default_transition_length,
const double transitioning_angle,
const double wall_split_middle_threshold,
const double wall_add_middle_threshold,
const int distribution_radius);
DistributedBeadingStrategy(coord_t optimal_width,
coord_t default_transition_length,
double transitioning_angle,
double wall_split_middle_threshold,
double wall_add_middle_threshold,
int distribution_radius);
virtual ~DistributedBeadingStrategy() override {}
~DistributedBeadingStrategy() 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;
};

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <cassert>
@ -26,7 +26,7 @@ float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) c
}
LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent)
: BeadingStrategy(parent->getOptimalWidth(), /*default_transition_length=*/-1, parent->getTransitioningAngle())
: BeadingStrategy(*parent)
, max_bead_count(max_bead_count)
, parent(std::move(parent))
{
@ -65,15 +65,12 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes
ret.total_thickness = thickness;
// Enforce symmetry
if (bead_count % 2 == 1)
{
if (bead_count % 2 == 1) {
ret.toolpath_locations[bead_count / 2] = thickness / 2;
ret.bead_widths[bead_count / 2] = thickness - optimal_thickness;
}
for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++)
{
ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx];
}
//Create a "fake" inner wall with 0 width to indicate the edge of the walled area.
//This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls.
@ -95,9 +92,7 @@ LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thicknes
coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
if (bead_count <= max_bead_count)
{
return parent->getOptimalThickness(bead_count);
}
assert(false);
return scaled<coord_t>(1000.); // 1 meter (Cura was returning 10 meter)
}
@ -105,13 +100,11 @@ coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
if (lower_bead_count < max_bead_count)
{
return parent->getTransitionThickness(lower_bead_count);
}
if (lower_bead_count == max_bead_count)
{
return parent->getOptimalThickness(lower_bead_count + 1) - scaled<coord_t>(0.01);
}
assert(false);
return scaled<coord_t>(900.); // 0.9 meter;
}
@ -119,12 +112,9 @@ coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count)
coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
{
coord_t parent_bead_count = parent->getOptimalBeadCount(thickness);
if (parent_bead_count <= max_bead_count)
{
if (parent_bead_count <= max_bead_count) {
return parent->getOptimalBeadCount(thickness);
}
else if (parent_bead_count == max_bead_count + 1)
{
} else if (parent_bead_count == max_bead_count + 1) {
if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled<coord_t>(0.01))
return max_bead_count;
else

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIMITED_BEADING_STRATEGY_H
@ -26,15 +26,15 @@ namespace Slic3r::Arachne
class LimitedBeadingStrategy : public BeadingStrategy
{
public:
LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent);
LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent);
virtual ~LimitedBeadingStrategy() override = default;
~LimitedBeadingStrategy() 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;
virtual std::string toString() const override;
std::string toString() const override;
coord_t getTransitioningLength(coord_t lower_bead_count) const override;

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "OuterWallInsetBeadingStrategy.hpp"
@ -7,15 +7,12 @@
namespace Slic3r::Arachne
{
OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent) :
BeadingStrategy(parent->getOptimalWidth(), parent->getDefaultTransitionLength(), parent->getTransitioningAngle()),
parent(std::move(parent)),
outer_wall_offset(outer_wall_offset)
OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent)
: BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset)
{
name = "OuterWallOfsetBeadingStrategy";
}
coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
return parent->getOptimalThickness(bead_count);

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H
@ -16,7 +16,7 @@ namespace Slic3r::Arachne
public:
OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent);
virtual ~OuterWallInsetBeadingStrategy() = default;
~OuterWallInsetBeadingStrategy() override = default;
Beading compute(coord_t thickness, coord_t bead_count) const override;

View file

@ -1,4 +1,4 @@
//Copyright (c) 2021 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "RedistributeBeadingStrategy.hpp"
@ -9,35 +9,40 @@
namespace Slic3r::Arachne
{
RedistributeBeadingStrategy::RedistributeBeadingStrategy( const coord_t optimal_width_outer,
const coord_t optimal_width_inner,
const double minimum_variable_line_width,
BeadingStrategyPtr parent) :
BeadingStrategy(parent->getOptimalWidth(), parent->getDefaultTransitionLength(), parent->getTransitioningAngle()),
parent(std::move(parent)),
optimal_width_outer(optimal_width_outer),
optimal_width_inner(optimal_width_inner),
minimum_variable_line_width(minimum_variable_line_width)
RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer,
const double minimum_variable_line_ratio,
BeadingStrategyPtr parent)
: BeadingStrategy(*parent)
, parent(std::move(parent))
, optimal_width_outer(optimal_width_outer)
, minimum_variable_line_ratio(minimum_variable_line_ratio)
{
name = "RedistributeBeadingStrategy";
}
coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const
{
const coord_t inner_bead_count = bead_count > 2 ? bead_count - 2 : 0;
const coord_t inner_bead_count = std::max(static_cast<coord_t>(0), bead_count - 2);
const coord_t outer_bead_count = bead_count - inner_bead_count;
return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count;
}
coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
return parent->getTransitionThickness(lower_bead_count);
switch (lower_bead_count) {
case 0: return minimum_variable_line_ratio * optimal_width_outer;
case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer;
default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer;
}
}
coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
{
return parent->getOptimalBeadCount(thickness);
if (thickness < minimum_variable_line_ratio * optimal_width_outer)
return 0;
if (thickness <= 2 * optimal_width_outer)
return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1;
return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2;
}
coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
@ -58,123 +63,35 @@ std::string RedistributeBeadingStrategy::toString() const
BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
{
Beading ret;
if (bead_count > 2)
{
const coord_t inner_transition_width = optimal_width_inner * minimum_variable_line_width;
const coord_t outer_bead_width =
getOptimalOuterBeadWidth(thickness, optimal_width_outer, inner_transition_width);
// Outer wall is locked in size and position for wall regions of 3 and higher which have at least a
// thickness equal to two times the optimal outer width and the minimal inner wall width.
const coord_t virtual_thickness = thickness - outer_bead_width * 2;
const coord_t virtual_bead_count = bead_count - 2;
// Calculate the beads and widths of the inner walls only
ret = parent->compute(virtual_thickness, virtual_bead_count);
// Insert the outer beads
ret.bead_widths.insert(ret.bead_widths.begin(), outer_bead_width);
ret.bead_widths.emplace_back(outer_bead_width);
}
else
{
ret = parent->compute(thickness, bead_count);
// Take care of all situations in which no lines are actually produced:
if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) {
ret.left_over = thickness;
ret.total_thickness = thickness;
return ret;
}
// Filter out beads that violate the minimum inner wall widths and recompute if necessary
const coord_t outer_transition_width = optimal_width_inner * minimum_variable_line_width;
const bool removed_inner_beads = validateInnerBeadWidths(ret, outer_transition_width);
if (removed_inner_beads)
{
ret = compute(thickness, bead_count - 1);
// Compute the beadings of the inner walls, if any:
const coord_t inner_bead_count = bead_count - 2;
const coord_t inner_thickness = thickness - 2 * optimal_width_outer;
if (inner_bead_count > 0 && inner_thickness > 0) {
ret = parent->compute(inner_thickness, inner_bead_count);
for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer;
}
// Ensure that the positions of the beads are distributed over the thickness
resetToolPathLocations(ret, thickness);
// Insert the outer wall(s) around the previously computed inner wall(s), which may be empty:
const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count;
ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness);
ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2);
if (bead_count > 1) {
ret.bead_widths.push_back(actual_outer_thickness);
ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2);
}
// Ensure correct total and left over thickness.
ret.total_thickness = thickness;
ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast<coord_t>(0));
return ret;
}
coord_t RedistributeBeadingStrategy::getOptimalOuterBeadWidth(const coord_t thickness, const coord_t optimal_width_outer, const coord_t minimum_width_inner)
{
const coord_t total_outer_optimal_width = optimal_width_outer * 2;
coord_t outer_bead_width = thickness / 2;
if (total_outer_optimal_width < thickness)
{
if (total_outer_optimal_width + minimum_width_inner > thickness)
{
outer_bead_width -= minimum_width_inner / 2;
}
else
{
outer_bead_width = optimal_width_outer;
}
}
return outer_bead_width;
}
void RedistributeBeadingStrategy::resetToolPathLocations(BeadingStrategy::Beading& beading, const coord_t thickness)
{
const size_t bead_count = beading.bead_widths.size();
beading.toolpath_locations.resize(bead_count);
if (bead_count < 1)
{
beading.toolpath_locations.resize(0);
beading.total_thickness = thickness;
beading.left_over = thickness;
return;
}
// Update the first half of the toolpath-locations with the updated bead-widths (starting from 0, up to half):
coord_t last_coord = 0;
coord_t last_width = 0;
for (size_t i_location = 0; i_location < bead_count / 2; ++i_location)
{
beading.toolpath_locations[i_location] = last_coord + (last_width + beading.bead_widths[i_location]) / 2;
last_coord = beading.toolpath_locations[i_location];
last_width = beading.bead_widths[i_location];
}
// Handle the position of any middle wall (note that the width will already have been set correctly):
if (bead_count % 2 == 1)
{
beading.toolpath_locations[bead_count / 2] = thickness / 2;
}
// Update the last half of the toolpath-locations with the updated bead-widths (starting from thickness, down to half):
last_coord = thickness;
last_width = 0;
for (size_t i_location = bead_count - 1; i_location >= bead_count - (bead_count / 2); --i_location)
{
beading.toolpath_locations[i_location] = last_coord - (last_width + beading.bead_widths[i_location]) / 2;
last_coord = beading.toolpath_locations[i_location];
last_width = beading.bead_widths[i_location];
}
// Ensure correct total and left over thickness
beading.total_thickness = thickness;
beading.left_over = thickness - std::accumulate(beading.bead_widths.cbegin(), beading.bead_widths.cend(), static_cast<coord_t>(0));
}
bool RedistributeBeadingStrategy::validateInnerBeadWidths(BeadingStrategy::Beading& beading, const coord_t minimum_width_inner)
{
// Filter out bead_widths that violate the transition width and recalculate if needed
const size_t unfiltered_beads = beading.bead_widths.size();
if(unfiltered_beads <= 2) //Outer walls are exempt. If there are 2 walls the range below will be empty. If there is 1 or 0 walls it would be invalid.
{
return false;
}
auto inner_begin = std::next(beading.bead_widths.begin());
auto inner_end = std::prev(beading.bead_widths.end());
beading.bead_widths.erase(
std::remove_if(inner_begin, inner_end,
[&minimum_width_inner](const coord_t width)
{
return width < minimum_width_inner;
}),
inner_end);
return unfiltered_beads != beading.bead_widths.size();
}
} // namespace Slic3r::Arachne

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
@ -30,17 +30,11 @@ namespace Slic3r::Arachne
* /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a
* bead count if the parent strategies' optimum bead width is a weighted
* average of the outer and inner walls at that bead count.
* /param optimal_width_outer Inner wall width, guaranteed to be the actual (save rounding errors) at a
* bead count if the parent strategies' optimum bead width is a weighted
* average of the outer and inner walls at that bead count.
* /param minimum_variable_line_width Minimum factor that the variable line might deviate from the optimal width.
* /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width.
*/
RedistributeBeadingStrategy(const coord_t optimal_width_outer,
const coord_t optimal_width_inner,
const double minimum_variable_line_width,
BeadingStrategyPtr parent);
RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent);
virtual ~RedistributeBeadingStrategy() override = default;
~RedistributeBeadingStrategy() override = default;
Beading compute(coord_t thickness, coord_t bead_count) const override;
@ -53,45 +47,9 @@ namespace Slic3r::Arachne
std::string toString() const override;
protected:
/*!
* Determine the outer bead width.
*
* According to the following logic:
* - If the thickness of the model is more then twice the optimal outer bead width and the minimum inner bead
* width it will return the optimal outer bead width.
* - If the thickness is less then twice the optimal outer bead width and the minimum inner bead width, but
* more them twice the optimal outer bead with it will return the optimal bead width minus half the inner bead
* width.
* - If the thickness is less then twice the optimal outer bead width it will return half the thickness as
* outer bead width
*
* \param thickness Thickness of the total beads.
* \param optimal_width_outer User specified optimal outer bead width.
* \param minimum_width_inner Inner bead width times the minimum variable line width.
* \return The outer bead width.
*/
static coord_t getOptimalOuterBeadWidth(coord_t thickness, coord_t optimal_width_outer, coord_t minimum_width_inner);
/*!
* Moves the beads towards the outer edges of thickness and ensures that the outer walls are locked in location
* \param beading The beading instance.
* \param thickness The thickness of the bead.
*/
static void resetToolPathLocations(Beading& beading, coord_t thickness);
/*!
* Filters and validates the beads, to ensure that all inner beads are at least the minimum bead width.
*
* \param beading The beading instance.
* \param minimum_width_inner Inner bead width times the minimum variable line width.
* \return true if beads are removed.
*/
static bool validateInnerBeadWidths(Beading& beading, coord_t minimum_width_inner);
BeadingStrategyPtr parent;
coord_t optimal_width_outer;
coord_t optimal_width_inner;
double minimum_variable_line_width;
double minimum_variable_line_ratio;
};
} // namespace Slic3r::Arachne

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "WideningBeadingStrategy.hpp"
@ -7,7 +7,7 @@ namespace Slic3r::Arachne
{
WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width)
: BeadingStrategy(parent->getOptimalWidth(), /*default_transition_length=*/-1, parent->getTransitioningAngle())
: BeadingStrategy(*parent)
, parent(std::move(parent))
, min_input_width(min_input_width)
, min_output_width(min_output_width)
@ -21,23 +21,18 @@ std::string WideningBeadingStrategy::toString() const
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
{
if (thickness < optimal_width)
{
if (thickness < optimal_width) {
Beading ret;
ret.total_thickness = thickness;
if (thickness >= min_input_width)
{
ret.bead_widths.emplace_back(std::max(thickness, min_output_width));
ret.toolpath_locations.emplace_back(thickness / 2);
}
else
{
} else {
ret.left_over = thickness;
}
return ret;
}
else
{
} else {
return parent->compute(thickness, bead_count);
}
}
@ -50,20 +45,18 @@ coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const
coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
{
if (lower_bead_count == 0)
{
return min_input_width;
}
else
{
return parent->getTransitionThickness(lower_bead_count);
}
}
coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
{
if (thickness < min_input_width) return 0;
if (thickness < min_input_width)
return 0;
coord_t ret = parent->getOptimalBeadCount(thickness);
if (thickness >= min_input_width && ret < 1) return 1;
if (thickness >= min_input_width && ret < 1)
return 1;
return ret;
}

View file

@ -1,4 +1,4 @@
//Copyright (c) 2020 Ultimaker B.V.
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef WIDENING_BEADING_STRATEGY_H
@ -23,18 +23,18 @@ public:
/*!
* Takes responsibility for deleting \param parent
*/
WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width);
WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width);
virtual ~WideningBeadingStrategy() override = default;
~WideningBeadingStrategy() override = default;
virtual Beading compute(coord_t thickness, coord_t bead_count) const override;
virtual coord_t getOptimalThickness(coord_t bead_count) const override;
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const override;
virtual coord_t getOptimalBeadCount(coord_t thickness) const override;
virtual coord_t getTransitioningLength(coord_t lower_bead_count) const override;
virtual float getTransitionAnchorPos(coord_t lower_bead_count) const override;
virtual std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const override;
virtual std::string toString() const override;
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;
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;
std::string toString() const override;
protected:
BeadingStrategyPtr parent;

View file

@ -542,7 +542,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
separatePointyQuadEndNodes();
graph.fixNodeDuplication();
graph.collapseSmallEdges();
// Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
@ -606,7 +605,7 @@ static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoid
}
#endif
void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_toolpaths, bool filter_outermost_central_edges)
void SkeletalTrapezoidation::generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges)
{
p_generated_toolpaths = &generated_toolpaths;
@ -625,11 +624,7 @@ void SkeletalTrapezoidation::generateToolpaths(VariableWidthPaths& generated_too
generateExtraRibs();
markRegions();
generateSegments();
liftRegionInfoToLines();
}
void SkeletalTrapezoidation::updateIsCentral()
@ -1496,52 +1491,6 @@ void SkeletalTrapezoidation::generateExtraRibs()
//
// ^^^^^^^^^^^^^^^^^^^^^
// TRANSTISIONING
// =====================
void SkeletalTrapezoidation::markRegions()
{
// Painters algorithm, loop over all edges and skip those that have already been 'painted' with a region.
size_t region = 0; // <- Region zero is 'None', it will be incremented before the first edge.
for (edge_t& edge : graph.edges)
{
if (edge.data.regionIsSet())
{
continue;
}
// An edge that didn't have a region painted is encountered, so make a new region and start a worklist:
++region;
std::queue<STHalfEdge*> worklist;
worklist.push(&edge);
// Loop over all edges that are connected to this one, except don't cross any medial axis edges:
while (!worklist.empty())
{
edge_t* p_side = worklist.front();
worklist.pop();
edge_t* p_next = p_side;
do
{
if (!p_next->data.regionIsSet())
{
p_next->data.setRegion(region);
if(p_next->twin != nullptr && (p_next->next == nullptr || p_next->prev == nullptr))
{
worklist.push(p_next->twin);
}
}
else
{
assert(region == p_next->data.getRegion());
}
p_next = p_next->next;
} while (p_next != nullptr && p_next != p_side);
}
}
}
// =====================
// TOOLPATH GENERATION
// vvvvvvvvvvvvvvvvvvvvv
@ -1891,7 +1840,7 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t<BeadingPropagation>&
{ // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly
junction = a;
}
ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx, edge_.data.getRegion());
ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx);
}
}
}
@ -1979,11 +1928,11 @@ std::shared_ptr<SkeletalTrapezoidationJoint::BeadingPropagation> SkeletalTrapezo
return nullptr;
}
void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path)
void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way)
{
if (from == to) return;
VariableWidthPaths& generated_toolpaths = *p_generated_toolpaths;
std::vector<VariableWidthLines> &generated_toolpaths = *p_generated_toolpaths;
size_t inset_idx = from.perimeter_index;
if (inset_idx >= generated_toolpaths.size())
@ -1991,24 +1940,33 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c
generated_toolpaths.resize(inset_idx + 1);
}
assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated");
if (!force_new_path
&& !generated_toolpaths[inset_idx].empty()
&& generated_toolpaths[inset_idx].back().is_odd == is_odd
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled<coord_t>(0.01))
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled<coord_t>(0.01)
)
if (generated_toolpaths[inset_idx].empty()
|| generated_toolpaths[inset_idx].back().is_odd != is_odd
|| generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent
)
{
generated_toolpaths[inset_idx].back().junctions.push_back(from);
force_new_path = true;
}
else if (!force_new_path
&& !generated_toolpaths[inset_idx].empty()
&& generated_toolpaths[inset_idx].back().is_odd == is_odd
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled<coord_t>(0.01))
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled<coord_t>(0.01)
)
if (!force_new_path
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled<coord_t>(0.010))
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled<coord_t>(0.010)
&& ! from_is_3way // force new path at 3way intersection
)
{
generated_toolpaths[inset_idx].back().junctions.push_back(to);
}
else if (!force_new_path
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled<coord_t>(0.010))
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled<coord_t>(0.010)
&& ! to_is_3way // force new path at 3way intersection
)
{
if ( ! is_odd)
{
BOOST_LOG_TRIVIAL(error) << "Reversing even wall line causes it to be printed CCW instead of CW!";
}
generated_toolpaths[inset_idx].back().junctions.push_back(from);
}
else
{
generated_toolpaths[inset_idx].emplace_back(inset_idx, is_odd);
@ -2027,13 +1985,14 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_
unprocessed_quad_starts.insert(&edge);
}
}
std::unordered_set<edge_t*> passed_odd_edges;
while (!unprocessed_quad_starts.empty())
{
edge_t* poly_domain_start = *unprocessed_quad_starts.begin();
edge_t* quad_start = poly_domain_start;
bool new_domain_start = true;
do
{
edge_t* quad_end = quad_start;
@ -2045,20 +2004,22 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_
edge_t* edge_to_peak = getQuadMaxRedgeTo(quad_start);
// walk down on both sides and connect junctions
edge_t* edge_from_peak = edge_to_peak->next; assert(edge_from_peak);
unprocessed_quad_starts.erase(quad_start);
if (! edge_to_peak->data.hasExtrusionJunctions())
{
edge_junctions.emplace_back(std::make_shared<LineJunctions>());
edge_to_peak->data.setExtrusionJunctions(edge_junctions.back());
}
// The junctions on the edge(s) from the start of the quad to the node with highest R
LineJunctions from_junctions = *edge_to_peak->data.getExtrusionJunctions();
if (! edge_from_peak->twin->data.hasExtrusionJunctions())
{
edge_junctions.emplace_back(std::make_shared<LineJunctions>());
edge_from_peak->twin->data.setExtrusionJunctions(edge_junctions.back());
}
// The junctions on the edge(s) from the end of the quad to the node with highest R
LineJunctions to_junctions = *edge_from_peak->twin->data.getExtrusionJunctions();
if (edge_to_peak->prev)
{
@ -2106,22 +2067,29 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_
{
BOOST_LOG_TRIVIAL(warning) << "Connecting two perimeters with different indices! Perimeter " << from.perimeter_index << " and " << to.perimeter_index;
}
const bool is_odd_segment = edge_to_peak->to->data.bead_count > 0 && edge_to_peak->to->data.bead_count % 2 == 1 // quad contains single bead segment
&& edge_to_peak->to->data.transition_ratio == 0 && edge_to_peak->from->data.transition_ratio == 0 && edge_from_peak->to->data.transition_ratio == 0 // We're not in a transition
const bool from_is_odd =
quad_start->to->data.bead_count > 0 && quad_start->to->data.bead_count % 2 == 1 // quad contains single bead segment
&& quad_start->to->data.transition_ratio == 0 // We're not in a transition
&& junction_rev_idx == segment_count - 1 // Is single bead segment
&& shorter_then(from.p - quad_start->to->p, scaled<coord_t>(0.005)) && shorter_then(to.p - quad_end->from->p, scaled<coord_t>(0.005));
&& shorter_then(from.p - quad_start->to->p, scaled<coord_t>(0.005));
const bool to_is_odd =
quad_end->from->data.bead_count > 0 && quad_end->from->data.bead_count % 2 == 1 // quad contains single bead segment
&& quad_end->from->data.transition_ratio == 0 // We're not in a transition
&& junction_rev_idx == segment_count - 1 // Is single bead segment
&& shorter_then(to.p - quad_end->from->p, scaled<coord_t>(0.005));
const bool is_odd_segment = from_is_odd && to_is_odd;
if (is_odd_segment
&& passed_odd_edges.count(quad_start->next->twin) > 0) // Only generate toolpath for odd segments once
{
continue; // Prevent duplication of single bead segments
}
bool from_is_3way = from_is_odd && quad_start->to->isMultiIntersection();
bool to_is_3way = to_is_odd && quad_end->from->isMultiIntersection();
passed_odd_edges.emplace(quad_start->next);
const bool force_new_path = is_odd_segment && quad_start->to->isMultiIntersection();
addToolpathSegment(from, to, is_odd_segment, force_new_path);
addToolpathSegment(from, to, is_odd_segment, new_domain_start, from_is_3way, to_is_3way);
}
new_domain_start = false;
}
while(quad_start = quad_start->getNextUnconnected(), quad_start != poly_domain_start);
}
@ -2129,7 +2097,7 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_
void SkeletalTrapezoidation::generateLocalMaximaSingleBeads()
{
VariableWidthPaths& generated_toolpaths = *p_generated_toolpaths;
std::vector<VariableWidthLines> &generated_toolpaths = *p_generated_toolpaths;
for (auto& node : graph.nodes)
{
@ -2141,7 +2109,6 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads()
if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && !node.isCentral())
{
const size_t inset_index = beading.bead_widths.size() / 2;
const size_t& region_id = node.incident_edge->data.getRegion();
constexpr bool is_odd = true;
if (inset_index >= generated_toolpaths.size())
{
@ -2149,24 +2116,21 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads()
}
generated_toolpaths[inset_index].emplace_back(inset_index, is_odd);
ExtrusionLine& line = generated_toolpaths[inset_index].back();
line.junctions.emplace_back(node.p, beading.bead_widths[inset_index], inset_index, region_id);
line.junctions.emplace_back(node.p + Point(50, 0), beading.bead_widths[inset_index], inset_index, region_id);
// TODO: ^^^ magic value ... + Point(50, 0) ^^^
const coord_t width = beading.bead_widths[inset_index];
// total area to be extruded is pi*(w/2)^2 = pi*w*w/4
// Width a constant extrusion width w, that would be a length of pi*w/4
// If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r
// So our circle needs to be such that r=w/8
const coord_t r = width / 8;
constexpr coord_t n_segments = 6;
for (coord_t segment = 0; segment < n_segments; segment++) {
float a = 2.0 * M_PI / n_segments * segment;
line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index);
}
}
}
}
void SkeletalTrapezoidation::liftRegionInfoToLines()
{
std::for_each(p_generated_toolpaths->begin(), p_generated_toolpaths->end(), [](VariableWidthLines& lines)
{
std::for_each(lines.begin(), lines.end(), [](ExtrusionLine& line)
{
line.region_id = line.junctions.front().region_id;
});
});
}
//
// ^^^^^^^^^^^^^^^^^^^^^
// TOOLPATH GENERATION

View file

@ -24,16 +24,16 @@ namespace Slic3r::Arachne
/*!
* Main class of the dynamic beading strategies.
*
*
* The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure.
*
*
* We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy,
* and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified]
*
*
* The method can be visually explained as generating the 3D union of cones surface on the outline polygons,
* and changing the heights along central regions of that surface so that they are flat.
* and changing the heights along central regions of that surface so that they are flat.
* For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused
deposition modeling" by Kuipers et al.
deposition modeling" by Kuipers et al.
* This visual explanation aid explains the use of "upward", "lower" etc,
* i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'.
*
@ -93,7 +93,7 @@ public:
* beadings propagated from below and from above, use this transitioning
* distance.
*/
SkeletalTrapezoidation(const Polygons& polys,
SkeletalTrapezoidation(const Polygons& polys,
const BeadingStrategy& beading_strategy,
double transitioning_angle
, coord_t discretization_step_size = scaled<coord_t>(0.0008)
@ -118,7 +118,7 @@ public:
* "central" but as if it's a obtuse corner. As a result, sharp corners will
* no longer end in a single line but will just loop.
*/
void generateToolpaths(VariableWidthPaths& generated_toolpaths, bool filter_outermost_central_edges = false);
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
protected:
/*!
@ -136,14 +136,14 @@ protected:
/*!
* Compute the skeletal trapezoidation decomposition of the input shape.
*
*
* Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure.
*
*
* The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered,
* which means that there is no one-to-one mapping from VD edges to HE edges.
* Instead we map from a VD edge to the last HE edge.
* This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards.
*
*
* Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers.
* We therefore collapse edges and their whole cells afterwards.
*/
@ -160,7 +160,7 @@ protected:
/*!
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
*/
VariableWidthPaths* p_generated_toolpaths;
std::vector<VariableWidthLines> *p_generated_toolpaths;
/*!
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
@ -261,7 +261,7 @@ protected:
/*!
* Filter out small central areas.
*
*
* Only used to get rid of small edges which get marked as central because
* of rounding errors because the region is so small.
*/
@ -277,9 +277,9 @@ protected:
/*!
* Unmark the outermost edges directly connected to the outline, as not
* being central.
*
*
* Only used to emulate some related literature.
*
*
* The paper shows that this function is bad for the stability of the framework.
*/
void filterOuterCentral();
@ -458,12 +458,6 @@ protected:
// ^ transitioning ^
/*!
* It's useful to know when the paths get back to the consumer, to (what part of) a polygon the paths 'belong'.
* A single polygon without a hole is one region, a polygon with (a) hole(s) has 2 regions.
*/
void markRegions();
// v toolpath generation v
/*!
@ -473,7 +467,7 @@ protected:
/*!
* From a quad (a group of linked edges in one cell of the Voronoi), find
* the edge that is furthest away from the border of the polygon.
* the edge pointing to the node that is furthest away from the border of the polygon.
* \param quad_start_edge The first edge of the quad.
* \return The edge of the quad that is furthest away from the border.
*/
@ -482,27 +476,27 @@ protected:
/*!
* Propagate beading information from nodes that are closer to the edge
* (low radius R) to nodes that are farther from the edge (high R).
*
*
* only propagate from nodes with beading info upward to nodes without beading info
*
*
* Edges are sorted by their radius, so that we can do a depth-first walk
* without employing a recursive algorithm.
*
*
* In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.)
*
*
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
*/
void propagateBeadingsUpward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
/*!
* propagate beading info from higher R nodes to lower R nodes
*
*
* merge with upward propagated beadings if they are encountered
*
*
* don't transfer to nodes which lie on the outline polygon
*
*
* edges are sorted so that we can do a depth-first walk without employing a recursive algorithm
*
*
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
*/
void propagateBeadingsDownward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
@ -573,9 +567,16 @@ protected:
void generateJunctions(ptr_vector_t<BeadingPropagation>& node_beadings, ptr_vector_t<LineJunctions>& edge_junctions);
/*!
* add a new toolpath segment, defined between two extrusion-juntions
* Add a new toolpath segment, defined between two extrusion-juntions.
*
* \param from The junction from which to add a segment.
* \param to The junction to which to add a segment.
* \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton.
* \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from
* \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together.
* \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together.
*/
void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path);
void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way);
/*!
* connect junctions in each quad
@ -586,11 +587,6 @@ protected:
* Genrate small segments for local maxima where the beading would only result in a single bead
*/
void generateLocalMaximaSingleBeads();
/*!
* Extract region information from the junctions, for easier access to that info directly from the lines.
*/
void liftRegionInfoToLines();
};
} // namespace Slic3r::Arachne

View file

@ -52,14 +52,8 @@ public:
};
EdgeType type;
SkeletalTrapezoidationEdge()
: SkeletalTrapezoidationEdge(EdgeType::NORMAL)
{}
SkeletalTrapezoidationEdge(const EdgeType& type)
: type(type)
, is_central(Central::UNKNOWN)
, region(0)
{}
SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {}
SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {}
bool isCentral() const
{
@ -75,21 +69,6 @@ public:
return is_central != Central::UNKNOWN;
}
size_t getRegion() const
{
assert(region != 0);
return region;
}
void setRegion(const size_t& r)
{
assert(region == 0);
region = r;
}
bool regionIsSet() const
{
return region > 0;
}
bool hasTransitions(bool ignore_empty = false) const
{
return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty());
@ -131,7 +110,6 @@ public:
private:
Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown
size_t region; //! what 'region' this edge is in ... if the originating polygon has no holes, there's one region -- useful for later algorithms that need to know where the paths came from
std::weak_ptr<std::list<TransitionMiddle>> transitions;
std::weak_ptr<std::list<TransitionEnd>> transition_ends;

View file

@ -128,6 +128,10 @@ bool STHalfEdgeNode::isMultiIntersection()
edge_t* outgoing = this->incident_edge;
do
{
if ( ! outgoing)
{ // This is a node on the outside
return false;
}
if (outgoing->data.isCentral())
{
odd_path_count++;
@ -174,39 +178,6 @@ bool STHalfEdgeNode::isLocalMaximum(bool strict) const
return true;
}
void SkeletalTrapezoidationGraph::fixNodeDuplication()
{
for (auto node_it = nodes.begin(); node_it != nodes.end();)
{
node_t* replacing_node = nullptr;
for (edge_t* outgoing = node_it->incident_edge; outgoing != node_it->incident_edge; outgoing = outgoing->twin->next)
{
assert(outgoing);
if (outgoing->from != &*node_it)
{
replacing_node = outgoing->from;
}
if (outgoing->twin->to != &*node_it)
{
replacing_node = outgoing->twin->to;
}
}
if (replacing_node)
{
for (edge_t* outgoing = node_it->incident_edge; outgoing != node_it->incident_edge; outgoing = outgoing->twin->next)
{
outgoing->twin->to = replacing_node;
outgoing->from = replacing_node;
}
node_it = nodes.erase(node_it);
}
else
{
++node_it;
}
}
}
void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
{
std::unordered_map<edge_t*, std::list<edge_t>::iterator> edge_locator;

View file

@ -70,7 +70,6 @@ class SkeletalTrapezoidationGraph: public HalfEdgeGraph<SkeletalTrapezoidationJo
using edge_t = STHalfEdge;
using node_t = STHalfEdgeNode;
public:
void fixNodeDuplication();
/*!
* If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph.

View file

@ -1,4 +1,4 @@
// Copyright (c) 2020 Ultimaker B.V.
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include <algorithm> //For std::partition_copy and std::min_element.
@ -8,10 +8,15 @@
#include "SkeletalTrapezoidation.hpp"
#include "../ClipperUtils.hpp"
#include "Arachne/utils/linearAlg2D.hpp"
#include "utils/linearAlg2D.hpp"
#include "EdgeGrid.hpp"
#include "utils/SparseLineGrid.hpp"
#include "Geometry.hpp"
#include "utils/PolylineStitcher.hpp"
#include "SVG.hpp"
#include "Utils.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r::Arachne
{
@ -40,7 +45,6 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0
, bead_width_x(bead_width_x)
, inset_count(inset_count)
, wall_0_inset(wall_0_inset)
, strategy_type(print_object_config.beading_strategy_type.value)
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
, min_feature_size(scaled<coord_t>(print_object_config.min_feature_size.value))
, min_bead_width(scaled<coord_t>(print_object_config.min_bead_width.value))
@ -225,21 +229,6 @@ void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled<coor
}
}
/*!
* Locator to extract a line segment out of a \ref PolygonsPointIndex
*/
struct PolygonsPointIndexSegmentLocator
{
std::pair<Point, Point> operator()(const PolygonsPointIndex &val) const
{
const Polygon &poly = (*val.polygons)[val.poly_idx];
const Point start = poly[val.point_idx];
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
const Point end = poly[next_point_idx];
return std::pair<Point, Point>(start, end);
}
};
typedef SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator> LocToLineGrid;
std::unique_ptr<LocToLineGrid> createLocToLineGrid(const Polygons &polygons, int square_size)
{
@ -309,7 +298,7 @@ void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
*/
void removeDegenerateVerts(Polygons &thiss)
{
for (unsigned int poly_idx = 0; poly_idx < thiss.size(); poly_idx++) {
for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) {
Polygon &poly = thiss[poly_idx];
Polygon result;
@ -319,14 +308,17 @@ void removeDegenerateVerts(Polygons &thiss)
return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm();
};
bool isChanged = false;
for (unsigned int idx = 0; idx < poly.size(); idx++) {
for (size_t idx = 0; idx < poly.size(); idx++) {
const Point &last = (result.size() == 0) ? poly.back() : result.back();
if (idx + 1 == poly.size() && result.size() == 0) { break; }
Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1];
if (idx + 1 == poly.size() && result.size() == 0)
break;
const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1];
if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction
// don't add vert to the result
isChanged = true;
while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next)) { result.points.pop_back(); }
while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next))
result.points.pop_back();
} else {
result.points.emplace_back(poly[idx]);
}
@ -486,7 +478,7 @@ void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0
}
}
const VariableWidthPaths& WallToolPaths::generate()
const std::vector<VariableWidthLines> &WallToolPaths::generate()
{
const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution;
const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation;
@ -506,42 +498,49 @@ const VariableWidthPaths& WallToolPaths::generate()
removeDegenerateVerts(prepared_outline);
removeSmallAreas(prepared_outline, small_area_length * small_area_length, false);
if (area(prepared_outline) > 0)
{
const coord_t wall_transition_length = scaled<coord_t>(this->print_object_config.wall_transition_length.value);
const double wall_split_middle_threshold = this->print_object_config.wall_split_middle_threshold.value / 100.; // For an uneven nr. of lines: When to split the middle wall into two.
const double wall_add_middle_threshold = this->print_object_config.wall_add_middle_threshold.value / 100.; // For an even nr. of lines: When to add a new middle in between the innermost two walls.
const int wall_distribution_count = this->print_object_config.wall_distribution_count.value;
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
const auto beading_strat = BeadingStrategyFactory::makeStrategy
(
strategy_type,
bead_width_0,
bead_width_x,
wall_transition_length,
transitioning_angle,
print_thin_walls,
min_bead_width,
min_feature_size,
wall_split_middle_threshold,
wall_add_middle_threshold,
max_bead_count,
wall_0_inset,
wall_distribution_count
);
const coord_t transition_filter_dist = scaled<coord_t>(this->print_object_config.wall_transition_filter_distance.value);
SkeletalTrapezoidation wall_maker
(
prepared_outline,
*beading_strat,
beading_strat->getTransitioningAngle(),
discretization_step_size,
transition_filter_dist,
wall_transition_length
);
wall_maker.generateToolpaths(toolpaths);
computeInnerContour();
if (area(prepared_outline) <= 0) {
assert(toolpaths.empty());
return toolpaths;
}
const coord_t wall_transition_length = scaled<coord_t>(this->print_object_config.wall_transition_length.value);
const double wall_split_middle_threshold = this->print_object_config.wall_split_middle_threshold.value / 100.; // For an uneven nr. of lines: When to split the middle wall into two.
const double wall_add_middle_threshold = this->print_object_config.wall_add_middle_threshold.value / 100.; // For an even nr. of lines: When to add a new middle in between the innermost two walls.
const int wall_distribution_count = this->print_object_config.wall_distribution_count.value;
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
const auto beading_strat = BeadingStrategyFactory::makeStrategy
(
bead_width_0,
bead_width_x,
wall_transition_length,
transitioning_angle,
print_thin_walls,
min_bead_width,
min_feature_size,
wall_split_middle_threshold,
wall_add_middle_threshold,
max_bead_count,
wall_0_inset,
wall_distribution_count
);
const coord_t transition_filter_dist = scaled<coord_t>(this->print_object_config.wall_transition_filter_distance.value);
SkeletalTrapezoidation wall_maker
(
prepared_outline,
*beading_strat,
beading_strat->getTransitioningAngle(),
discretization_step_size,
transition_filter_dist,
wall_transition_length
);
wall_maker.generateToolpaths(toolpaths);
stitchToolPaths(toolpaths, this->bead_width_x);
removeSmallLines(toolpaths);
separateOutInnerContour();
simplifyToolPaths(toolpaths);
removeEmptyToolPaths(toolpaths);
@ -554,7 +553,127 @@ const VariableWidthPaths& WallToolPaths::generate()
return toolpaths;
}
void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Settings& settings*/)
void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, const coord_t bead_width_x)
{
const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width.
for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) {
VariableWidthLines& wall_lines = toolpaths[wall_idx];
VariableWidthLines stitched_polylines;
VariableWidthLines closed_polygons;
PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance);
#ifdef DEBUG
for (const ExtrusionLine& line : stitched_polylines) {
if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) {
BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!";
assert(false && "Some even contour lines could not be closed into polygons!");
BoundingBox aabb;
for (auto line2 : wall_lines)
for (auto j : line2)
aabb.merge(j.p);
{
static int iRun = 0;
SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb);
std::array<const char *, 8> colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"};
size_t color_idx = 0;
for (auto& inset : toolpaths)
for (auto& line2 : inset) {
// svg.writePolyline(line2.toPolygon(), col);
Polygon poly = line2.toPolygon();
Point last = poly.front();
for (size_t idx = 1 ; idx < poly.size(); idx++) {
Point here = poly[idx];
svg.draw(Line(last, here), colors[color_idx]);
// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black");
last = here;
}
svg.draw(poly[0], colors[color_idx]);
// svg.nextLayer();
// svg.writePoints(poly, true, 0.1);
// svg.nextLayer();
color_idx = (color_idx + 1) % colors.size();
}
}
{
static int iRun = 0;
SVG svg(debug_out_path("contours-%d.svg", iRun), aabb);
for (auto& inset : toolpaths)
for (auto& line2 : inset)
svg.draw_outline(line2.toPolygon(), "gray");
for (auto& line2 : stitched_polylines) {
const char *col = line2.is_odd ? "gray" : "red";
if ( ! line2.is_odd)
std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n";
if ( ! line2.is_odd)
svg.draw(line2.front().p);
Polygon poly = line2.toPolygon();
Point last = poly.front();
for (size_t idx = 1 ; idx < poly.size(); idx++)
{
Point here = poly[idx];
svg.draw(Line(last, here), col);
last = here;
}
}
for (auto line2 : closed_polygons)
svg.draw(line2.toPolygon());
}
}
}
#endif // DEBUG
wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines
for (ExtrusionLine& wall_polygon : closed_polygons)
{
if (wall_polygon.junctions.empty())
{
continue;
}
wall_polygon.is_closed = true;
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
}
#ifdef DEBUG
for (ExtrusionLine& line : wall_lines)
{
assert(line.inset_idx == wall_idx);
}
#endif // DEBUG
}
}
template<typename T> bool shorterThan(const T &shape, const coord_t check_length)
{
const auto *p0 = &shape.back();
int64_t length = 0;
for (const auto &p1 : shape) {
length += (*p0 - p1).template cast<int64_t>().norm();
if (length >= check_length)
return false;
p0 = &p1;
}
return true;
}
void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines> &toolpaths)
{
for (VariableWidthLines &inset : toolpaths) {
for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) {
ExtrusionLine &line = inset[line_idx];
coord_t min_width = std::numeric_limits<coord_t>::max();
for (const ExtrusionJunction &j : line)
min_width = std::min(min_width, j.w);
if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line
line = std::move(inset.back());
inset.erase(--inset.end());
line_idx--; // reconsider the current position
}
}
}
}
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths)
{
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
{
@ -568,57 +687,62 @@ void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Set
}
}
const VariableWidthPaths& WallToolPaths::getToolPaths()
const std::vector<VariableWidthLines> &WallToolPaths::getToolPaths()
{
if (!toolpaths_generated)
{
return generate();
}
return toolpaths;
}
void WallToolPaths::computeInnerContour()
void WallToolPaths::separateOutInnerContour()
{
//We'll remove all 0-width paths from the original toolpaths and store them separately as polygons.
VariableWidthPaths actual_toolpaths;
std::vector<VariableWidthLines> actual_toolpaths;
actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude.
VariableWidthPaths contour_paths;
std::vector<VariableWidthLines> contour_paths;
contour_paths.reserve(toolpaths.size() / inset_count);
std::partition_copy(toolpaths.begin(), toolpaths.end(), std::back_inserter(actual_toolpaths), std::back_inserter(contour_paths),
[](const VariableWidthLines& path)
{
for(const ExtrusionLine& line : path)
{
for(const ExtrusionJunction& junction : line.junctions)
{
return junction.w != 0; //On the first actual junction, decide: If it's got 0 width, this is a contour. Otherwise it is an actual toolpath.
}
}
return true; //No junctions with any vertices? Classify it as a toolpath then.
});
if (! actual_toolpaths.empty())
{
toolpaths = std::move(actual_toolpaths); //Filtered out the 0-width paths.
}
else
{
toolpaths.clear();
}
//Now convert the contour_paths to Polygons to denote the inner contour of the walled areas.
inner_contour.clear();
for (const VariableWidthLines &inset : toolpaths) {
if (inset.empty())
continue;
bool is_contour = false;
for (const ExtrusionLine &line : inset) {
for (const ExtrusionJunction &j : line) {
if (j.w == 0)
is_contour = true;
else
is_contour = false;
break;
}
}
//We're going to have to stitch these paths since not all walls may be closed contours.
//Since these walls have 0 width they should theoretically be closed. But there may be rounding errors.
const coord_t minimum_line_width = bead_width_0 / 2;
stitchContours(contour_paths, minimum_line_width, inner_contour);
if (is_contour) {
#ifdef DEBUG
for (const ExtrusionLine &line : inset)
for (const ExtrusionJunction &j : line)
assert(j.w == 0);
#endif // DEBUG
for (const ExtrusionLine &line : inset) {
if (line.is_odd)
continue; // odd lines don't contribute to the contour
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 {
actual_toolpaths.emplace_back(inset);
}
}
if (!actual_toolpaths.empty())
toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths.
else
toolpaths.clear();
//The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines.
//They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative.
//To get a correct shape, we need to make the outside contour positive and any holes inside negative.
//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);
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
}
const Polygons& WallToolPaths::getInnerContour()
@ -634,7 +758,7 @@ const Polygons& WallToolPaths::getInnerContour()
return inner_contour;
}
bool WallToolPaths::removeEmptyToolPaths(VariableWidthPaths& toolpaths)
bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths)
{
toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines)
{
@ -643,222 +767,99 @@ bool WallToolPaths::removeEmptyToolPaths(VariableWidthPaths& toolpaths)
return toolpaths.empty();
}
void WallToolPaths::stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output)
/*!
* Get the order constraints of the insets when printing walls per region / hole.
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
*
* 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.
*/
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> WallToolPaths::getRegionOrder(const std::vector<const ExtrusionLine *> &input, const bool outer_to_inner)
{
// Create a bucket grid to find endpoints that are close together.
struct ExtrusionLineStartLocator
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> order_requirements;
// We build a grid where we map toolpath vertex locations to toolpaths,
// so that we can easily find which two toolpaths are next to each other,
// which is the requirement for there to be an order constraint.
//
// We use a PointGrid rather than a LineGrid to save on computation time.
// In very rare cases two insets might lie next to each other without having neighboring vertices, e.g.
// \ .
// | / .
// | / .
// || .
// | \ .
// | \ .
// / .
// However, because of how Arachne works this will likely never be the case for two consecutive insets.
// On the other hand one could imagine that two consecutive insets of a very large circle
// could be simplify()ed such that the remaining vertices of the two insets don't align.
// In those cases the order requirement is not captured,
// which means that the PathOrderOptimizer *might* result in a violation of the user set path order.
// This problem is expected to be not so severe and happen very sparsely.
coord_t max_line_w = 0u;
for (const ExtrusionLine *line : input) // compute max_line_w
for (const ExtrusionJunction &junction : *line)
max_line_w = std::max(max_line_w, junction.w);
if (max_line_w == 0u)
return order_requirements;
struct LineLoc
{
const Point *operator()(const ExtrusionLine *line) { return &line->junctions.front().p; }
ExtrusionJunction j;
const ExtrusionLine *line;
};
struct ExtrusionLineEndLocator
struct Locator
{
const Point *operator()(const ExtrusionLine *line) { return &line->junctions.back().p; }
Point operator()(const LineLoc &elem) { return elem.j.p; }
};
// Only find endpoints closer than minimum_line_width, so we can't ever accidentally make crossing contours.
ClosestPointInRadiusLookup<const ExtrusionLine*, ExtrusionLineStartLocator> line_starts(coord_t(stitch_distance * std::sqrt(2.)));
ClosestPointInRadiusLookup<const ExtrusionLine*, ExtrusionLineEndLocator> line_ends(coord_t(stitch_distance * std::sqrt(2.)));
// How much farther two verts may be apart due to corners.
// This distance must be smaller than 2, because otherwise
// we could create an order requirement between e.g.
// wall 2 of one region and wall 3 of another region,
// while another wall 3 of the first region would lie in between those two walls.
// However, higher values are better against the limitations of using a PointGrid rather than a LineGrid.
constexpr float diagonal_extension = 1.9f;
const auto searching_radius = coord_t(max_line_w * diagonal_extension);
using GridT = SparsePointGrid<LineLoc, Locator>;
GridT grid(searching_radius);
auto get_search_bbox = [](const Point &pt, const coord_t radius) -> BoundingBox {
const Point min_grid((pt - Point(radius, radius)) / radius);
const Point max_grid((pt + Point(radius, radius)) / radius);
return {min_grid * radius, (max_grid + Point(1, 1)) * radius - Point(1, 1)};
};
for (const VariableWidthLines &path : input) {
for (const ExtrusionLine &line : path) {
line_starts.insert(&line);
line_ends.insert(&line);
}
}
//Then go through all lines and construct chains of polylines if the endpoints are nearby.
std::unordered_set<const ExtrusionLine*> processed_lines; //Track which lines were already processed to not process them twice.
for(const VariableWidthLines& path : input)
{
for(const ExtrusionLine& line : path)
{
if(processed_lines.find(&line) != processed_lines.end()) //We already added this line before. It got added as a nearby line.
{
for (const ExtrusionLine *line : input)
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
const LineLoc &lineloc_here = pair.second;
const ExtrusionLine *here = lineloc_here.line;
Point loc_here = pair.second.j.p;
std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
for (const LineLoc &lineloc_nearby : nearby_verts) {
const ExtrusionLine *nearby = lineloc_nearby.line;
if (nearby == here)
continue;
}
//We'll create a chain of polylines that get joined together. We can add polylines on both ends!
std::deque<const ExtrusionLine*> chain;
std::deque<bool> is_reversed; //Lines could need to be inserted in reverse. Must coincide with the `chain` deque.
const ExtrusionLine* nearest = &line; //At every iteration, add the polyline that joins together most closely.
bool nearest_reverse = false; //Whether the next line to insert must be inserted in reverse.
bool nearest_before = false; //Whether the next line to insert must be inserted in the front of the chain.
while(nearest)
{
if(processed_lines.find(nearest) != processed_lines.end())
{
break; //Looping. This contour is already processed.
}
processed_lines.insert(nearest);
if(nearest_before)
{
chain.push_front(nearest);
is_reversed.push_front(nearest_reverse);
}
else
{
chain.push_back(nearest);
is_reversed.push_back(nearest_reverse);
}
//Find any nearby lines to attach. Look on both ends of our current chain and find both ends of polylines.
const Point chain_start = is_reversed.front() ? chain.front()->junctions.back().p : chain.front()->junctions.front().p;
const Point chain_end = is_reversed.back() ? chain.back()->junctions.front().p : chain.back()->junctions.back().p;
std::vector<std::pair<const ExtrusionLine *const *, double>> starts_near_start = line_starts.find_all(chain_start);
std::vector<std::pair<const ExtrusionLine *const *, double>> ends_near_start = line_ends.find_all(chain_start);
std::vector<std::pair<const ExtrusionLine *const *, double>> starts_near_end = line_starts.find_all(chain_end);
std::vector<std::pair<const ExtrusionLine *const *, double>> ends_near_end = line_ends.find_all(chain_end);
nearest = nullptr;
int64_t nearest_dist2 = std::numeric_limits<int64_t>::max();
for (const auto &candidate_ptr : starts_near_start) {
const ExtrusionLine* candidate = *candidate_ptr.first;
if(processed_lines.find(candidate) != processed_lines.end())
continue; //Already processed this line before. It's linked to something else.
if (const int64_t dist2 = (candidate->junctions.front().p - chain_start).cast<int64_t>().squaredNorm(); dist2 < nearest_dist2) {
nearest = candidate;
nearest_dist2 = dist2;
nearest_reverse = true;
nearest_before = true;
}
}
for (const auto &candidate_ptr : ends_near_start) {
const ExtrusionLine* candidate = *candidate_ptr.first;
if(processed_lines.find(candidate) != processed_lines.end())
continue;
if (const int64_t dist2 = (candidate->junctions.back().p - chain_start).cast<int64_t>().squaredNorm(); dist2 < nearest_dist2) {
nearest = candidate;
nearest_dist2 = dist2;
nearest_reverse = false;
nearest_before = true;
}
}
for (const auto &candidate_ptr : starts_near_end) {
const ExtrusionLine* candidate = *candidate_ptr.first;
if(processed_lines.find(candidate) != processed_lines.end())
continue; //Already processed this line before. It's linked to something else.
if (const int64_t dist2 = (candidate->junctions.front().p - chain_start).cast<int64_t>().squaredNorm(); dist2 < nearest_dist2) {
nearest = candidate;
nearest_dist2 = dist2;
nearest_reverse = false;
nearest_before = false;
}
}
for (const auto &candidate_ptr : ends_near_end) {
const ExtrusionLine* candidate = *candidate_ptr.first;
if (processed_lines.find(candidate) != processed_lines.end())
continue;
if (const int64_t dist2 = (candidate->junctions.back().p - chain_start).cast<int64_t>().squaredNorm(); dist2 < nearest_dist2) {
nearest = candidate;
nearest_dist2 = dist2;
nearest_reverse = true;
nearest_before = false;
}
}
}
//Now serialize the entire chain into one polygon.
output.emplace_back();
for (size_t i = 0; i < chain.size(); ++i) {
if(!is_reversed[i])
for (const ExtrusionJunction& junction : chain[i]->junctions)
output.back().points.emplace_back(junction.p);
else
for (auto junction = chain[i]->junctions.rbegin(); junction != chain[i]->junctions.rend(); ++junction)
output.back().points.emplace_back(junction->p);
}
}
}
}
size_t getOuterRegionId(const Arachne::VariableWidthPaths& toolpaths, size_t& out_max_region_id)
{
// Polygons show up here one by one, so there are always only a) the outer lines and b) the lines that are part of the holes.
// Therefore, the outer-regions' lines will always have the region-id that is larger then all of the other ones.
// First, build the bounding boxes:
std::map<size_t, BoundingBox> region_ids_to_bboxes; // Use a sorted map, ordered by region_id, so that we can find the largest region_id quickly.
for (const Arachne::VariableWidthLines &path : toolpaths) {
for (const Arachne::ExtrusionLine &line : path) {
BoundingBox &aabb =
region_ids_to_bboxes[line.region_id]; // Empty AABBs are default initialized when region_ids are encountered for the first time.
for (const auto &junction : line.junctions) aabb.merge(junction.p);
}
}
// Then, the largest of these will be the one that's needed for the outer region, the others' all belong to hole regions:
BoundingBox outer_bbox;
size_t outer_region_id = 0; // Region-ID 0 is reserved for 'None'.
for (const auto &region_id_bbox_pair : region_ids_to_bboxes) {
if (region_id_bbox_pair.second.contains(outer_bbox)) {
outer_bbox = region_id_bbox_pair.second;
outer_region_id = region_id_bbox_pair.first;
}
}
// Maximum Region-ID (using the ordering of the map)
out_max_region_id = region_ids_to_bboxes.empty() ? 0 : region_ids_to_bboxes.rbegin()->first;
return outer_region_id;
}
Arachne::BinJunctions variableWidthPathToBinJunctions(const Arachne::VariableWidthPaths& toolpaths, const bool pack_regions_by_inset, const bool center_last, std::set<size_t>* p_bins_with_index_zero_insets)
{
// Find the largest inset-index:
size_t max_inset_index = 0;
for (const Arachne::VariableWidthLines &path : toolpaths)
max_inset_index = std::max(path.front().inset_idx, max_inset_index);
// Find which regions are associated with the outer-outer walls (which region is the one the rest is holes inside of):
size_t max_region_id = 0;
const size_t outer_region_id = getOuterRegionId(toolpaths, max_region_id);
//Since we're (optionally!) splitting off in the outer and inner regions, it may need twice as many bins as inset-indices.
//Add two extra bins for the center-paths, if they need to be stored separately. One bin for inner and one for outer walls.
const size_t max_bin = (pack_regions_by_inset ? (max_region_id * 2) + 2 : (max_inset_index + 1) * 2) + center_last * 2;
Arachne::BinJunctions insets(max_bin + 1);
for (const Arachne::VariableWidthLines &path : toolpaths) {
if (path.empty()) // Don't bother printing these.
continue;
const size_t inset_index = path.front().inset_idx;
// Convert list of extrusion lines to vectors of extrusion junctions, and add those to the binned insets.
for (const Arachne::ExtrusionLine &line : path) {
// Sort into the right bin, ...
size_t bin_index;
const bool in_hole_region = line.region_id != outer_region_id && line.region_id != 0;
if (center_last && line.is_odd) {
bin_index = inset_index > 0;
} else if (pack_regions_by_inset) {
bin_index = std::min(inset_index, static_cast<size_t>(1)) + 2 * (in_hole_region ? line.region_id : 0) + center_last * 2;
if (nearby->inset_idx == here->inset_idx)
continue;
if (nearby->inset_idx > here->inset_idx + 1)
continue; // not directly adjacent
if (here->inset_idx > nearby->inset_idx + 1)
continue; // not directly adjacent
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
continue; // points are too far away from each other
if (here->is_odd || nearby->is_odd) {
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
order_requirements.emplace(std::make_pair(nearby, here));
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
order_requirements.emplace(std::make_pair(here, nearby));
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
order_requirements.emplace(std::make_pair(nearby, here));
} else {
bin_index = inset_index + (in_hole_region ? (max_inset_index + 1) : 0) + center_last * 2;
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
order_requirements.emplace(std::make_pair(here, nearby));
}
insets[bin_index].emplace_back(line.junctions.begin(), line.junctions.end());
// Collect all bins that have zero-inset indices in them, if needed:
if (inset_index == 0 && p_bins_with_index_zero_insets != nullptr)
p_bins_with_index_zero_insets->insert(bin_index);
}
}
return insets;
}
BinJunctions WallToolPaths::getBinJunctions(std::set<size_t> &bins_with_index_zero_insets)
{
if (!toolpaths_generated)
generate();
return variableWidthPathToBinJunctions(toolpaths, true, true, &bins_with_index_zero_insets);
return order_requirements;
}
} // namespace Slic3r::Arachne

View file

@ -5,6 +5,7 @@
#define CURAENGINE_WALLTOOLPATHS_H
#include <memory>
#include <unordered_set>
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
#include "utils/ExtrusionLine.hpp"
@ -45,22 +46,26 @@ public:
* Generates the Toolpaths
* \return A reference to the newly create ToolPaths
*/
const VariableWidthPaths& generate();
const std::vector<VariableWidthLines> &generate();
/*!
* Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths
* \return a reference to the toolpaths
*/
const VariableWidthPaths& getToolPaths();
const std::vector<VariableWidthLines> &getToolPaths();
BinJunctions getBinJunctions(std::set<size_t> &bins_with_index_zero_insets);
/*!
* Alternate 'get', for when the vector that'll be inserted in already exists.
* \param The already existing (or empty) paths these new toolpaths are pushed into.
*/
void pushToolPaths(std::vector<VariableWidthLines> &paths);
/*!
* Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins.
* The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of
* infill with extra infill walls.
*/
void computeInnerContour();
void separateOutInnerContour();
/*!
* Gets the inner contour of the area which is inside of the generated tool
@ -81,32 +86,38 @@ public:
* \param toolpaths the VariableWidthPaths generated with \p generate()
* \return true if there are still paths left. If all toolpaths were removed it returns false
*/
static bool removeEmptyToolPaths(VariableWidthPaths& toolpaths);
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
/*!
* Stitches toolpaths together to form contours.
* Get the order constraints of the insets when printing walls per region / hole.
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
*
* All toolpaths are used. Paths that are not closed will get closed in the
* output by virtue of becoming polygons. As such, the input is expected to
* consist of almost completely closed contours, which may be split up into
* different polylines.
* This function combines those polylines into the polygons they are
* probably intended to depict.
* \param input The paths to stitch together.
* \param stitch_distance Any endpoints closer than this distance can be
* stitched together. An additional line segment will bridge the gap.
* \param output Where to store the output polygons.
* 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.
*/
static void stitchContours(const VariableWidthPaths& input, const coord_t stitch_distance, Polygons& output) ;
static std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> getRegionOrder(const std::vector<const ExtrusionLine *> &input, bool outer_to_inner);
protected:
/*!
* Stitch the polylines together and form closed polygons.
*
* Works on both toolpaths and inner contours simultaneously.
*/
static void stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, coord_t bead_width_x);
/*!
* Remove polylines shorter than half the smallest line width along that polyline.
*/
static void removeSmallLines(std::vector<VariableWidthLines> &toolpaths);
/*!
* Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided
* settings.
* \param settings The settings as provided by the user
* \return
*/
static void simplifyToolPaths(VariableWidthPaths& toolpaths/*, const Settings& settings*/);
static void simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths);
private:
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
@ -114,13 +125,12 @@ private:
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
size_t inset_count; //<! The maximum number of walls to generate
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
BeadingStrategyType strategy_type; //<! The wall generating strategy
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
bool toolpaths_generated; //<! Are the toolpaths generated
VariableWidthPaths toolpaths; //<! The generated toolpaths
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
Polygons inner_contour; //<! The inner contour of the generated toolpaths
const PrintObjectConfig &print_object_config;
};

View file

@ -13,11 +13,6 @@ bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const
&& perimeter_index == other.perimeter_index;
}
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const size_t region_id)
: p(p),
w(w),
perimeter_index(perimeter_index),
region_id(region_id)
{}
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
}

View file

@ -37,14 +37,7 @@ struct ExtrusionJunction
*/
size_t perimeter_index;
/*!
* Which region this juntion is part of. A solid polygon without holes has only one region.
* A polygon with holes has 2. Disconnected parts of the polygon are also separate regions.
* Will be 0 if no region was given.
*/
size_t region_id;
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const size_t region_id = 0);
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
bool operator==(const ExtrusionJunction& other) const;
};
@ -60,9 +53,7 @@ inline const Point& make_point(const ExtrusionJunction& ej)
return ej.p;
}
using LineJunctions = std::vector<ExtrusionJunction>; //<! Vector of Lines
using PathJunctions = std::vector<LineJunctions>; //<! Vector of paths
using BinJunctions = std::vector<PathJunctions>; //<! Vector of insets (bins)
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
}
#endif // UTILS_EXTRUSION_JUNCTION_H

View file

@ -9,25 +9,22 @@
namespace Slic3r::Arachne
{
ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id)
: inset_idx(inset_idx)
, is_odd(is_odd)
, region_id(region_id)
{}
ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {}
coord_t ExtrusionLine::getLength() const
{
if (junctions.empty())
{
return 0;
}
coord_t len = 0;
coord_t len = 0;
ExtrusionJunction prev = junctions.front();
for (const ExtrusionJunction& next : junctions)
{
for (const ExtrusionJunction &next : junctions) {
len += (next.p - prev.p).cast<int64_t>().norm();
prev = next;
}
if (is_closed)
len += (front().p - back().p).cast<int64_t>().norm();
return len;
}
@ -40,17 +37,13 @@ coord_t ExtrusionLine::getMinimalWidth() const
})->w;
}
void ExtrusionLine::appendJunctionsTo(LineJunctions& result) const
{
result.insert(result.end(), junctions.begin(), junctions.end());
}
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
{
if (junctions.size() <= 3)
{
const size_t min_path_size = is_closed ? 3 : 2;
if (junctions.size() <= min_path_size)
return;
}
// TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.
/* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
* starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
@ -126,9 +119,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const
// We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
&& extrusion_area_error <= maximum_extrusion_area_deviation)
{
// Adjust the width of the entire P-N line as a weighted average of the widths of the P-C and C-N lines and
// then remove the current junction (vertex).
next.w = weighted_average_width;
// Remove the current junction (vertex).
continue;
}
@ -155,7 +146,7 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const
else
{
// New point seems like a valid one.
const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index, current.region_id);
const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
// If there was a previous point added, remove it.
if(!new_junctions.empty())
{
@ -208,8 +199,8 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A,
* | |--------------------------| | |***************************|
* | | ------------------------------------------
* --------------- ^ **************
* ^ C.w ^
* B.w new_width = weighted_average_width
* ^ B.w + C.w / 2 ^
* A.w + B.w / 2 new_width = weighted_average_width
*
*
* ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
@ -218,20 +209,22 @@ int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A,
* */
const int64_t ab_length = (B - A).cast<int64_t>().norm();
const int64_t bc_length = (C - B).cast<int64_t>().norm();
const int64_t width_diff = llabs(B.w - C.w);
const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w));
if (width_diff > 1)
{
// Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
// weighted average value.
assert(((int64_t(ab_length) * int64_t(B.w) + int64_t(bc_length) * int64_t(C.w)) / (C - A).cast<int64_t>().norm()) <= std::numeric_limits<int64_t>::max());
weighted_average_width = (ab_length * int64_t(B.w) + bc_length * int64_t(C.w)) / (C - A).cast<int64_t>().norm();
assert((double(llabs(B.w - weighted_average_width)) * double(ab_length) + double(llabs(C.w - weighted_average_width)) * double(bc_length)) <= double(std::numeric_limits<int64_t>::max()));
return int64_t(llabs(B.w - weighted_average_width)) * ab_length + int64_t(llabs(C.w - weighted_average_width)) * bc_length;
const int64_t ab_weight = (A.w + B.w) / 2;
const int64_t bc_weight = (B.w + C.w) / 2;
assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm()) <= std::numeric_limits<coord_t>::max());
weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm();
assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits<int64_t>::max()));
return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length;
}
else
{
// If the width difference is very small, then select the width of the segment that is longer
weighted_average_width = ab_length > bc_length ? B.w : C.w;
weighted_average_width = ab_length > bc_length ? A.w : B.w;
assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits<coord_t>::max());
assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits<coord_t>::max());
return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;

View file

@ -42,11 +42,20 @@ struct ExtrusionLine
bool is_odd;
/*!
* Which region this line is part of. A solid polygon without holes has only one region.
* A polygon with holes has 2. Disconnected parts of the polygon are also separate regions.
* Will be 0 if no region was given.
* Whether this is a closed polygonal path
*/
size_t region_id;
bool is_closed;
/*!
* Gets the number of vertices in this polygon.
* \return The number of vertices in this polygon.
*/
size_t size() const { return junctions.size(); }
/*!
* Whether there are no junctions.
*/
bool empty() const { return junctions.empty(); }
/*!
* The list of vertices along which this path runs.
@ -55,23 +64,79 @@ struct ExtrusionLine
*/
std::vector<ExtrusionJunction> junctions;
ExtrusionLine(const size_t inset_idx, const bool is_odd, const size_t region_id = 0);
ExtrusionLine(const size_t inset_idx, const bool is_odd);
ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {}
ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {}
ExtrusionLine &operator=(ExtrusionLine &&other)
{
junctions = std::move(other.junctions);
inset_idx = other.inset_idx;
is_odd = other.is_odd;
is_closed = other.is_closed;
return *this;
}
ExtrusionLine &operator=(const ExtrusionLine &other)
{
junctions = other.junctions;
inset_idx = other.inset_idx;
is_odd = other.is_odd;
is_closed = other.is_closed;
return *this;
}
std::vector<ExtrusionJunction>::const_iterator begin() const { return junctions.begin(); }
std::vector<ExtrusionJunction>::const_iterator end() const { return junctions.end(); }
std::vector<ExtrusionJunction>::const_reverse_iterator rbegin() const { return junctions.rbegin(); }
std::vector<ExtrusionJunction>::const_reverse_iterator rend() const { return junctions.rend(); }
std::vector<ExtrusionJunction>::const_reference front() const { return junctions.front(); }
std::vector<ExtrusionJunction>::const_reference back() const { return junctions.back(); }
const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; }
ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; }
std::vector<ExtrusionJunction>::iterator begin() { return junctions.begin(); }
std::vector<ExtrusionJunction>::iterator end() { return junctions.end(); }
std::vector<ExtrusionJunction>::reference front() { return junctions.front(); }
std::vector<ExtrusionJunction>::reference back() { return junctions.back(); }
template<typename... Args> void emplace_back(Args &&...args) { junctions.emplace_back(args...); }
void remove(unsigned int index) { junctions.erase(junctions.begin() + index); }
void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); }
template<class iterator>
std::vector<ExtrusionJunction>::iterator insert(std::vector<ExtrusionJunction>::const_iterator pos, iterator first, iterator last)
{
return junctions.insert(pos, first, last);
}
void clear() { junctions.clear(); }
void reverse() { std::reverse(junctions.begin(), junctions.end()); }
/*!
* Sum the total length of this path.
*/
coord_t getLength() const;
coord_t polylineLength() const { return getLength(); }
/*!
* Put all junction locations into a polygon object.
*
* When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon.
*/
Polygon toPolygon() const
{
Polygon ret;
for (const ExtrusionJunction &j : junctions)
ret.points.emplace_back(j.p);
return ret;
}
/*!
* Get the minimal width of this path
*/
coord_t getMinimalWidth() const;
/*!
* Export the included junctions as vector.
*/
void appendJunctionsTo(LineJunctions& result) const;
/*!
* Removes vertices of the ExtrusionLines to make sure that they are not too high
* resolution.
@ -122,10 +187,7 @@ struct ExtrusionLine
static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width);
};
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne for each Path
using VariableWidthPaths = std::vector<VariableWidthLines>; //<! The toolpaths generated by libArachne
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::LineJunctions &line_junctions)
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions)
{
assert(line_junctions.size() >= 2);
Slic3r::ThickPolyline out;
@ -145,7 +207,8 @@ static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::LineJunctio
return out;
}
static inline Polygon to_polygon(const ExtrusionLine& line) {
static inline Polygon to_polygon(const ExtrusionLine &line)
{
Polygon out;
assert(line.junctions.size() >= 3);
assert(line.junctions.front().p == line.junctions.back().p);
@ -155,5 +218,7 @@ static inline Polygon to_polygon(const ExtrusionLine& line) {
return out;
}
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
} // namespace Slic3r::Arachne
#endif // UTILS_EXTRUSION_LINE_H

View file

@ -13,16 +13,20 @@
namespace Slic3r::Arachne
{
// Identity function, used to be able to make templated algorithms where the input is sometimes points, sometimes things that contain or can be converted to points.
inline const Point &make_point(const Point &p) { return p; }
/*!
* A class for iterating over the points in one of the polygons in a \ref Polygons object
*/
class PolygonsPointIndex
template<typename Paths>
class PathsPointIndex
{
public:
/*!
* The polygons into which this index is indexing.
*/
const Polygons* polygons; // (pointer to const polygons)
const Paths* polygons; // (pointer to const polygons)
unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons
@ -35,7 +39,7 @@ public:
* needed. Since the `polygons` field is const you can't ever make this
* initialisation useful.
*/
PolygonsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {}
PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {}
/*!
* Constructs a new point index to a vertex of a polygon.
@ -43,35 +47,50 @@ public:
* \param poly_idx The index of the sub-polygon to point to.
* \param point_idx The index of the vertex in the sub-polygon.
*/
PolygonsPointIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx)
: polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {}
PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {}
/*!
* Copy constructor to copy these indices.
*/
PolygonsPointIndex(const PolygonsPointIndex& original) = default;
PathsPointIndex(const PathsPointIndex& original) = default;
Point p() const
{
if (!polygons)
return {0, 0};
return (*polygons)[poly_idx][point_idx];
return make_point((*polygons)[poly_idx][point_idx]);
}
/*!
* \brief Returns whether this point is initialised.
*/
bool initialized() const { return polygons; }
/*!
* Get the polygon to which this PolygonsPointIndex refers
*/
const Polygon &getPolygon() const { return (*polygons)[poly_idx]; }
/*!
* Test whether two iterators refer to the same polygon in the same polygon list.
*
*
* \param other The PolygonsPointIndex to test for equality
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
*/
bool operator==(const PolygonsPointIndex &other) const
bool operator==(const PathsPointIndex &other) const
{
return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx;
}
bool operator!=(const PolygonsPointIndex &other) const { return !(*this == other); }
bool operator<(const PolygonsPointIndex &other) const { return this->p() < other.p(); }
PolygonsPointIndex &operator=(const PolygonsPointIndex &other)
bool operator!=(const PathsPointIndex &other) const
{
return !(*this == other);
}
bool operator<(const PathsPointIndex &other) const
{
return this->p() < other.p();
}
PathsPointIndex &operator=(const PathsPointIndex &other)
{
polygons = other.polygons;
poly_idx = other.poly_idx;
@ -79,13 +98,13 @@ public:
return *this;
}
//! move the iterator forward (and wrap around at the end)
PolygonsPointIndex &operator++()
PathsPointIndex &operator++()
{
point_idx = (point_idx + 1) % (*polygons)[poly_idx].size();
return *this;
}
//! move the iterator backward (and wrap around at the beginning)
PolygonsPointIndex &operator--()
PathsPointIndex &operator--()
{
if (point_idx == 0)
point_idx = (*polygons)[poly_idx].size();
@ -93,21 +112,51 @@ public:
return *this;
}
//! move the iterator forward (and wrap around at the end)
PolygonsPointIndex next() const
PathsPointIndex next() const
{
PolygonsPointIndex ret(*this);
PathsPointIndex ret(*this);
++ret;
return ret;
}
//! move the iterator backward (and wrap around at the beginning)
PolygonsPointIndex prev() const
PathsPointIndex prev() const
{
PolygonsPointIndex ret(*this);
PathsPointIndex ret(*this);
--ret;
return ret;
}
};
using PolygonsPointIndex = PathsPointIndex<Polygons>;
/*!
* Locator to extract a line segment out of a \ref PolygonsPointIndex
*/
struct PolygonsPointIndexSegmentLocator
{
std::pair<Point, Point> operator()(const PolygonsPointIndex &val) const
{
const Polygon &poly = (*val.polygons)[val.poly_idx];
Point start = poly[val.point_idx];
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
Point end = poly[next_point_idx];
return std::pair<Point, Point>(start, end);
}
};
/*!
* Locator of a \ref PolygonsPointIndex
*/
template<typename Paths>
struct PathsPointIndexLocator
{
Point operator()(const PathsPointIndex<Paths>& val) const
{
return make_point(val.p());
}
};
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
}//namespace Slic3r::Arachne

View file

@ -0,0 +1,42 @@
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "PolylineStitcher.hpp"
#include "ExtrusionLine.hpp"
namespace Slic3r::Arachne {
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canReverse(const PathsPointIndex<VariableWidthLines> &ppi)
{
if ((*ppi.polygons)[ppi.poly_idx].is_odd)
return true;
else
return false;
}
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canReverse(const PathsPointIndex<Polygons> &)
{
return true;
}
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canConnect(const ExtrusionLine &a, const ExtrusionLine &b)
{
return a.is_odd == b.is_odd;
}
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canConnect(const Polygon &, const Polygon &)
{
return true;
}
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::isOdd(const ExtrusionLine &line)
{
return line.is_odd;
}
template<> bool PolylineStitcher<Polygons, Polygon, Point>::isOdd(const Polygon &)
{
return false;
}
} // namespace Slic3r::Arachne

View file

@ -0,0 +1,234 @@
//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef UTILS_POLYLINE_STITCHER_H
#define UTILS_POLYLINE_STITCHER_H
#include "SparsePointGrid.hpp"
#include "PolygonsPointIndex.hpp"
#include "../../Polygon.hpp"
#include <unordered_set>
#include <cassert>
namespace Slic3r::Arachne
{
/*!
* Class for stitching polylines into longer polylines or into polygons
*/
template<typename Paths, typename Path, typename Junction>
class PolylineStitcher
{
public:
/*!
* Stitch together the separate \p lines into \p result_lines and if they
* can be closed into \p result_polygons.
*
* Only introduce new segments shorter than \p max_stitch_distance, and
* larger than \p snap_distance but always try to take the shortest
* connection possible.
*
* Only stitch polylines into closed polygons if they are larger than 3 *
* \p max_stitch_distance, in order to prevent small segments to
* accidentally get closed into a polygon.
*
* \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not
* be closed into polygons.
*
* \note Resulting polylines and polygons are added onto the existing
* containers, so you can directly output onto a polygons container with
* existing polygons in it. However, you shouldn't call this function with
* the same parameter in \p lines as \p result_lines, because that would
* duplicate (some of) the polylines.
* \param lines The lines to stitch together.
* \param result_lines[out] The stitched parts that are not closed polygons
* will be stored in here.
* \param result_polygons[out] The stitched parts that were closed as
* polygons will be stored in here.
* \param max_stitch_distance The maximum distance that will be bridged to
* connect two lines.
* \param snap_distance Points closer than this distance are considered to
* be the same point.
*/
static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled<coord_t>(0.1), coord_t snap_distance = scaled<coord_t>(0.01))
{
if (lines.empty())
return;
SparsePointGrid<PathsPointIndex<Paths>, PathsPointIndexLocator<Paths>> grid(max_stitch_distance, lines.size() * 2);
// populate grid
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
{
const auto line = lines[line_idx];
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, 0));
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, line.size() - 1));
}
std::vector<bool> processed(lines.size(), false);
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
{
if (processed[line_idx])
{
continue;
}
processed[line_idx] = true;
const auto line = lines[line_idx];
bool should_close = isOdd(line);
Path chain = line;
bool closest_is_closing_polygon = false;
for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation.
{ // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed.
if (go_in_reverse_direction)
{ // try extending chain in the other direction
chain.reverse();
}
coord_t chain_length = chain.polylineLength();
while (true)
{
Point from = make_point(chain.back());
PathsPointIndex<Paths> closest;
coord_t closest_distance = std::numeric_limits<coord_t>::max();
grid.processNearby(from, max_stitch_distance,
std::function<bool (const PathsPointIndex<Paths>&)> (
[from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close]
(const PathsPointIndex<Paths>& nearby)->bool
{
bool is_closing_segment = false;
coord_t dist = (nearby.p().template cast<int64_t>() - from.template cast<int64_t>()).norm();
if (dist > max_stitch_distance)
{
return true; // keep looking
}
if ((nearby.p().template cast<int64_t>() - make_point(chain.front()).template cast<int64_t>()).squaredNorm() < snap_distance * snap_distance)
{
if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline
|| chain.size() <= 2) // don't make 2 vert polygons
{
return true; // look for a better next line
}
is_closing_segment = true;
if (!should_close)
{
dist += scaled<coord_t>(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately
// continue to see if closing segment is also the closest
// there might be a segment smaller than [max_stitch_distance] which closes the polygon better
}
else
{
dist -= scaled<coord_t>(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours.
//Continue to see if closing segment is also the closest.
}
}
else if (processed[nearby.poly_idx])
{ // it was already moved to output
return true; // keep looking for a connection
}
bool nearby_would_be_reversed = nearby.point_idx != 0;
nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction
if (!canReverse(nearby) && nearby_would_be_reversed)
{ // connecting the segment would reverse the polygon direction
return true; // keep looking for a connection
}
if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx]))
{
return true; // keep looking for a connection
}
if (dist < closest_distance)
{
closest_distance = dist;
closest = nearby;
closest_is_closing_polygon = is_closing_segment;
}
if (dist < snap_distance)
{ // we have found a good enough next line
return false; // stop looking for alternatives
}
return true; // keep processing elements
})
);
if (!closest.initialized() // we couldn't find any next line
|| closest_is_closing_polygon // we closed the polygon
)
{
break;
}
coord_t segment_dist = (make_point(chain.back()).template cast<int64_t>() - closest.p().template cast<int64_t>()).norm();
assert(segment_dist <= max_stitch_distance + scaled<coord_t>(0.01));
const size_t old_size = chain.size();
if (closest.point_idx == 0)
{
auto start_pos = (*closest.polygons)[closest.poly_idx].begin();
if (segment_dist < snap_distance)
{
++start_pos;
}
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end());
}
else
{
auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin();
if (segment_dist < snap_distance)
{
++start_pos;
}
chain.insert(chain.end(), (*closest.polygons)[closest.poly_idx].rbegin(), (*closest.polygons)[closest.poly_idx].rend());
}
for(size_t i = old_size; i < chain.size(); ++i) //Update chain length.
{
chain_length += (make_point(chain[i]).template cast<int64_t>() - make_point(chain[i - 1]).template cast<int64_t>()).norm();
}
should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it.
assert( ! processed[closest.poly_idx]);
processed[closest.poly_idx] = true;
}
if (closest_is_closing_polygon)
{
if (go_in_reverse_direction)
{ // re-reverse chain to retain original direction
// NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction
chain.reverse();
}
break; // don't consider reverse direction
}
}
if (closest_is_closing_polygon)
{
result_polygons.emplace_back(chain);
}
else
{
PathsPointIndex<Paths> ppi_here(&lines, line_idx, 0);
if ( ! canReverse(ppi_here))
{ // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true
// the polyline isn't allowed to be reversed, so we re-reverse it.
chain.reverse();
}
result_lines.emplace_back(chain);
}
}
}
/*!
* Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd)
*/
static bool canReverse(const PathsPointIndex<Paths> &polyline);
/*!
* Whether two paths are allowed to be connected.
* (Not true for an odd and an even wall.)
*/
static bool canConnect(const Path &a, const Path &b);
static bool isOdd(const Path &line);
};
} // namespace Slic3r::Arachne
#endif // UTILS_POLYLINE_STITCHER_H

View file

@ -0,0 +1,90 @@
// Copyright (c) 2016 Scott Lenser
// Copyright (c) 2020 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef UTILS_SPARSE_POINT_GRID_H
#define UTILS_SPARSE_POINT_GRID_H
#include <cassert>
#include <unordered_map>
#include <vector>
#include "SparseGrid.hpp"
namespace Slic3r::Arachne {
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
*
* \tparam ElemT The element type to store.
* \tparam Locator The functor to get the location from ElemT. Locator
* must have: Point operator()(const ElemT &elem) const
* which returns the location associated with val.
*/
template<class ElemT, class Locator> class SparsePointGrid : public SparseGrid<ElemT>
{
public:
using Elem = ElemT;
/*! \brief Constructs a sparse grid with the specified cell size.
*
* \param[in] cell_size The size to use for a cell (square) in the grid.
* Typical values would be around 0.5-2x of expected query radius.
* \param[in] elem_reserve Number of elements to research space for.
* \param[in] max_load_factor Maximum average load factor before rehashing.
*/
SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
/*! \brief Inserts elem into the sparse grid.
*
* \param[in] elem The element to be inserted.
*/
void insert(const Elem &elem);
/*!
* Get just any element that's within a certain radius of a point.
*
* Rather than giving a vector of nearby elements, this function just gives
* a single element, any element, in no particular order.
* \param query_pt The point to query for an object nearby.
* \param radius The radius of what is considered "nearby".
*/
const ElemT *getAnyNearby(const Point &query_pt, coord_t radius);
protected:
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
/*! \brief Accessor for getting locations from elements. */
Locator m_locator;
};
template<class ElemT, class Locator>
SparsePointGrid<ElemT, Locator>::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
template<class ElemT, class Locator>
void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
{
Point loc = m_locator(elem);
GridPoint grid_loc = SparseGrid<ElemT>::toGridPoint(loc.template cast<int64_t>());
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
}
template<class ElemT, class Locator>
const ElemT *SparsePointGrid<ElemT, Locator>::getAnyNearby(const Point &query_pt, coord_t radius)
{
const ElemT *ret = nullptr;
const std::function<bool(const ElemT &)> &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) {
if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) {
ret = &maybe_nearby;
return false;
}
return true;
};
SparseGrid<ElemT>::processNearby(query_pt, radius, process_func);
return ret;
}
} // namespace Slic3r::Arachne
#endif // UTILS_SPARSE_POINT_GRID_H

View file

@ -296,8 +296,6 @@ add_library(libslic3r STATIC
Arachne/BeadingStrategy/BeadingStrategy.cpp
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
Arachne/BeadingStrategy/BeadingStrategyFactory.cpp
Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.hpp
Arachne/BeadingStrategy/CenterDeviationBeadingStrategy.cpp
Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp
Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp
Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp
@ -316,10 +314,14 @@ add_library(libslic3r STATIC
Arachne/utils/HalfEdgeGraph.hpp
Arachne/utils/HalfEdgeNode.hpp
Arachne/utils/SparseGrid.hpp
Arachne/utils/SparsePointGrid.hpp
Arachne/utils/SparseLineGrid.hpp
Arachne/utils/SquareGrid.hpp
Arachne/utils/SquareGrid.cpp
Arachne/utils/PolygonsPointIndex.hpp
Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp
Arachne/utils/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp

View file

@ -570,6 +570,8 @@ Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExP
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType)
{ return to_polygons(clipper_do<ClipperLib::Paths>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), fillType, ApplySafetyOffset::No)); }
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)

View file

@ -447,6 +447,7 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::
Slic3r::Polygons union_(const Slic3r::Polygons &subject);
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject);
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType);
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2);
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);

View file

@ -6,6 +6,7 @@
#include <cmath>
#include <cassert>
#include <stack>
namespace Slic3r {
@ -318,9 +319,7 @@ void PerimeterGenerator::process_arachne()
Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, bead_width_x, coord_t(loop_number + 1), wall_0_inset, *this->object_config);
wallToolPaths.generate();
std::set<size_t> bins_with_index_zero_perimeters;
Arachne::BinJunctions perimeters = wallToolPaths.getBinJunctions(bins_with_index_zero_perimeters);
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
int start_perimeter = int(perimeters.size()) - 1;
int end_perimeter = -1;
int direction = -1;
@ -331,27 +330,70 @@ void PerimeterGenerator::process_arachne()
direction = 1;
}
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
if (perimeters[perimeter_idx].empty())
continue;
for (const Arachne::ExtrusionLine &wall : perimeters[perimeter_idx])
all_extrusions.emplace_back(&wall);
}
// Find topological order with constraints from extrusions_constrains.
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
std::unordered_map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, this->config->external_perimeters_first);
for (auto [before, after] : extrusions_constrains) {
auto after_it = map_extrusion_to_idx.find(after);
++blocked[after_it->second];
blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
}
std::stack<const Arachne::ExtrusionLine *> unblocked_extrusions;
// Add open extrusions before closed extrusions to ensure that on the top of the stack will be closed extrusions.
for (size_t extrusion_idx = 0; extrusion_idx < all_extrusions.size(); ++extrusion_idx)
if (!all_extrusions[extrusion_idx]->is_closed && blocked[extrusion_idx] == 0)
unblocked_extrusions.emplace(all_extrusions[extrusion_idx]);
for (size_t extrusion_idx = 0; extrusion_idx < all_extrusions.size(); ++extrusion_idx)
if (all_extrusions[extrusion_idx]->is_closed && blocked[extrusion_idx] == 0)
unblocked_extrusions.emplace(all_extrusions[extrusion_idx]);
std::vector<const Arachne::ExtrusionLine *> ordered_extrusions;
while (!unblocked_extrusions.empty()) {
const Arachne::ExtrusionLine *extrusion = unblocked_extrusions.top();
unblocked_extrusions.pop();
ordered_extrusions.emplace_back(extrusion);
for (const size_t blocking_idx : blocking[map_extrusion_to_idx.find(extrusion)->second]) {
--blocked[blocking_idx];
if (blocked[blocking_idx] == 0)
unblocked_extrusions.emplace(all_extrusions[blocking_idx]);
}
}
assert(ordered_extrusions.size() == all_extrusions.size());
for (const Arachne::ExtrusionLine *extrusion : ordered_extrusions) {
if (extrusion->empty())
continue;
ExtrusionEntityCollection entities_coll;
for (const Arachne::LineJunctions &ej : perimeters[perimeter_idx]) {
ThickPolyline thick_polyline = Arachne::to_thick_polyline(ej);
bool ext_perimeter = bins_with_index_zero_perimeters.count(perimeter_idx) > 0;
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(thick_polyline, ext_perimeter ? erExternalPerimeter : erPerimeter,
ext_perimeter ? this->ext_perimeter_flow : this->perimeter_flow, scaled<float>(0.05), 0);
// Append paths to collection.
if (!paths.empty()) {
if (paths.front().first_point() == paths.back().last_point())
entities_coll.entities.emplace_back(new ExtrusionLoop(std::move(paths)));
else {
for (ExtrusionPath &path : paths)
entities_coll.entities.emplace_back(new ExtrusionPath(std::move(path)));
}
}
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
bool ext_perimeter = extrusion->inset_idx == 0;
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(thick_polyline, ext_perimeter ? erExternalPerimeter : erPerimeter,
ext_perimeter ? this->ext_perimeter_flow : this->perimeter_flow, scaled<float>(0.05), 0);
// Append paths to collection.
if (!paths.empty()) {
if (paths.front().first_point() == paths.back().last_point())
entities_coll.entities.emplace_back(new ExtrusionLoop(std::move(paths)));
else
for (ExtrusionPath &path : paths)
entities_coll.entities.emplace_back(new ExtrusionPath(std::move(path)));
}
this->loops->append(entities_coll);
}

View file

@ -449,7 +449,7 @@ static std::vector<std::string> s_Preset_print_options {
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
"slicing_engine", "beading_strategy_type", "wall_transition_length", "wall_transition_filter_distance", "wall_transition_angle",
"slicing_engine", "wall_transition_length", "wall_transition_filter_distance", "wall_transition_angle",
"wall_distribution_count", "wall_split_middle_threshold", "wall_add_middle_threshold", "min_feature_size", "min_bead_width"
};

View file

@ -201,13 +201,6 @@ static t_config_enum_values s_keys_map_SlicingEngine {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingEngine)
static t_config_enum_values s_keys_map_BeadingStrategyType {
{ "center_deviation", int(BeadingStrategyType::Center) },
{ "distributed", int(BeadingStrategyType::Distributed) },
{ "inward_distributed", int(BeadingStrategyType::InwardDistributed) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeadingStrategyType)
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
{
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
@ -3064,27 +3057,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionEnum<SlicingEngine>(SlicingEngine::Classic));
def = this->add("beading_strategy_type", coEnum);
def->label = L("Variable Line Strategy");
def->category = L("Advanced");
def->tooltip = L("Strategy to use to print the width of a part with a number of walls. This determines "
"how many walls it will use for a certain total width, and how wide each of"
" these lines are. \"Center Deviation\" will print all walls at the nominal"
" line width except the central one(s), causing big variations in the center"
" but very consistent outsides. \"Distributed\" distributes the width equally"
" over all walls. \"Inward Distributed\" is a balance between the other two, "
"distributing the changes in width over all walls but keeping the walls on the"
" outside slightly more consistent.");
def->enum_keys_map = &ConfigOptionEnum<BeadingStrategyType>::get_enum_values();
def->enum_values.push_back("center_deviation");
def->enum_values.push_back("distributed");
def->enum_values.push_back("inward_distributed");
def->enum_labels.push_back(L("Center Deviation"));
def->enum_labels.push_back(L("Distributed"));
def->enum_labels.push_back(L("Inward Distributed"));
def->mode = comExpert;
def->set_default_value(new ConfigOptionEnum<BeadingStrategyType>(BeadingStrategyType::InwardDistributed));
def = this->add("wall_transition_length", coFloat);
def->label = L("Wall Transition Length");
def->category = L("Advanced");

View file

@ -136,15 +136,6 @@ enum class SlicingEngine
Arachne
};
enum class BeadingStrategyType
{
Center,
Distributed,
InwardDistributed,
None,
Count
};
#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
@ -168,7 +159,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingEngine)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeadingStrategyType)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@ -498,7 +488,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, slice_closing_radius))
((ConfigOptionEnum<SlicingMode>, slicing_mode))
((ConfigOptionEnum<SlicingEngine>, slicing_engine))
((ConfigOptionEnum<BeadingStrategyType>, beading_strategy_type))
((ConfigOptionFloat, wall_transition_length))
((ConfigOptionFloat, wall_transition_filter_distance))
((ConfigOptionFloat, wall_transition_angle))

View file

@ -663,7 +663,6 @@ bool PrintObject::invalidate_state_by_config_options(
}
} else if (
opt_key == "slicing_engine"
|| opt_key == "beading_strategy_type"
|| opt_key == "wall_transition_length"
|| opt_key == "wall_transition_filter_distance"
|| opt_key == "wall_transition_angle"

View file

@ -1672,7 +1672,6 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Experimental"));
optgroup->append_single_option_line("slicing_engine");
optgroup->append_single_option_line("beading_strategy_type");
optgroup->append_single_option_line("wall_transition_length");
optgroup->append_single_option_line("wall_transition_filter_distance");
optgroup->append_single_option_line("wall_transition_angle");