Updated Arachne with Cura master.
This commit is contained in:
parent
e99b579f93
commit
3610afd393
40 changed files with 1219 additions and 1170 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ®ion_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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal file
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal 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
|
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal file
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal 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
|
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal file
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue