Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/CuraEngine.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJelle Spijker <spijker.jelle@gmail.com>2022-04-07 12:49:28 +0300
committerJelle Spijker <spijker.jelle@gmail.com>2022-04-07 12:49:28 +0300
commited4701757c898f25f2c79073986e369eb36a718c (patch)
tree78ac3cf4bf64c1757a1958d72e90e209c1193435
parent07a3e1bd16bddeefae982afe27a61d74c6b0714d (diff)
parenteb2b17e687ec7376b28fb1d170ce3da4d6726d3e (diff)
Merge branch 'master' into CURA-8640_PyQt6_upgrade
# Conflicts: # CMakeLists.txt
-rw-r--r--CMakeLists.txt17
-rw-r--r--SECURITY.md5
-rw-r--r--src/BeadingStrategy/BeadingStrategy.cpp48
-rw-r--r--src/BeadingStrategy/BeadingStrategy.h22
-rw-r--r--src/BeadingStrategy/BeadingStrategyFactory.cpp60
-rw-r--r--src/BeadingStrategy/BeadingStrategyFactory.h14
-rw-r--r--src/BeadingStrategy/CenterDeviationBeadingStrategy.cpp88
-rw-r--r--src/BeadingStrategy/CenterDeviationBeadingStrategy.h50
-rw-r--r--src/BeadingStrategy/DistributedBeadingStrategy.cpp36
-rw-r--r--src/BeadingStrategy/DistributedBeadingStrategy.h25
-rw-r--r--src/BeadingStrategy/LimitedBeadingStrategy.cpp2
-rw-r--r--src/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp6
-rw-r--r--src/BeadingStrategy/RedistributeBeadingStrategy.cpp169
-rw-r--r--src/BeadingStrategy/RedistributeBeadingStrategy.h20
-rw-r--r--src/BeadingStrategy/WideningBeadingStrategy.cpp2
-rw-r--r--src/FffGcodeWriter.cpp65
-rw-r--r--src/FffPolygonGenerator.cpp135
-rw-r--r--src/InsetOrderOptimizer.cpp25
-rw-r--r--src/InsetOrderOptimizer.h4
-rw-r--r--src/LayerPlan.cpp194
-rw-r--r--src/LayerPlan.h89
-rw-r--r--src/PathOrder.cpp43
-rw-r--r--src/PathOrder.h80
-rw-r--r--src/PathOrderMonotonic.h7
-rw-r--r--src/PathOrderOptimizer.cpp51
-rw-r--r--src/PathOrderOptimizer.h143
-rw-r--r--src/PathOrderPath.cpp50
-rw-r--r--src/PathOrderPath.h107
-rw-r--r--src/SkeletalTrapezoidation.cpp8
-rw-r--r--src/SkeletalTrapezoidation.h6
-rw-r--r--src/SkeletalTrapezoidationGraph.cpp33
-rw-r--r--src/SkeletalTrapezoidationGraph.h1
-rw-r--r--src/SupportInfillPart.h2
-rw-r--r--src/TopSurface.cpp10
-rw-r--r--src/WallToolPaths.cpp35
-rw-r--r--src/WallToolPaths.h26
-rw-r--r--src/Wireframe2gcode.cpp2
-rw-r--r--src/gcodeExport.cpp3
-rw-r--r--src/infill.cpp57
-rw-r--r--src/infill.h17
-rw-r--r--src/pathPlanning/Comb.cpp26
-rw-r--r--src/pathPlanning/GCodePath.cpp9
-rw-r--r--src/pathPlanning/GCodePath.h6
-rw-r--r--src/settings/Settings.cpp29
-rw-r--r--src/skin.cpp8
-rw-r--r--src/sliceDataStorage.h6
-rw-r--r--src/support.cpp23
-rw-r--r--src/utils/ExtrusionLine.cpp18
-rw-r--r--src/utils/ExtrusionLine.h1
-rw-r--r--src/utils/PolygonConnector.cpp286
-rw-r--r--src/utils/PolygonConnector.h693
-rw-r--r--src/utils/PolylineStitcher.cpp12
-rw-r--r--src/utils/PolylineStitcher.h63
-rw-r--r--src/utils/SVG.cpp3
-rw-r--r--src/utils/SVG.h3
-rw-r--r--src/utils/math.h2
-rw-r--r--src/utils/polygonUtils.cpp96
-rw-r--r--src/utils/polygonUtils.h27
-rw-r--r--tests/ExtruderPlanTest.cpp75
-rw-r--r--tests/InfillTest.cpp2
-rw-r--r--tests/PathOrderMonotonicTest.cpp28
-rw-r--r--tests/WallsComputationTest.cpp3
-rw-r--r--tests/beading_strategy/CenterDeviationBeadingStrategyTest.cpp158
-rw-r--r--tests/utils/ExtrusionLineTest.cpp36
-rw-r--r--tests/utils/PolygonConnectorTest.cpp224
65 files changed, 1749 insertions, 1845 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f9f962f4..cdfda4c30 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,8 +52,7 @@ set(engine_SRCS # Except main.cpp.
src/MeshGroup.cpp
src/Mold.cpp
src/multiVolumes.cpp
- src/PathOrderOptimizer.cpp
- src/PathOrder.cpp
+ src/PathOrderPath.cpp
src/Preheat.cpp
src/PrimeTower.cpp
src/raft.cpp
@@ -78,7 +77,6 @@ set(engine_SRCS # Except main.cpp.
src/BeadingStrategy/BeadingStrategy.cpp
src/BeadingStrategy/BeadingStrategyFactory.cpp
- src/BeadingStrategy/CenterDeviationBeadingStrategy.cpp
src/BeadingStrategy/DistributedBeadingStrategy.cpp
src/BeadingStrategy/LimitedBeadingStrategy.cpp
src/BeadingStrategy/RedistributeBeadingStrategy.cpp
@@ -163,9 +161,6 @@ if (ENABLE_ARCUS)
ArcusCommunicationPrivateTest
)
endif ()
-set(engine_TEST_BEADING_STRATEGY
- CenterDeviationBeadingStrategyTest
- )
set(engine_TEST_INFILL
)
set(engine_TEST_INTEGRATION
@@ -342,14 +337,8 @@ if (BUILD_TESTS)
target_link_libraries(${test} _CuraEngine ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
add_test(NAME ${test} COMMAND "${test}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests/")
add_dependencies(build_all_tests ${test}) #Make sure that this gets built as part of the build_all_tests target.
- endforeach ()
- endif ()
- foreach (test ${engine_TEST_BEADING_STRATEGY})
- add_executable(${test} tests/main.cpp tests/beading_strategy/${test}.cpp)
- target_link_libraries(${test} _CuraEngine ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
- add_test(NAME ${test} COMMAND "${test}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests/")
- add_dependencies(build_all_tests ${test}) #Make sure that this gets built as part of the build_all_tests target.
- endforeach ()
+ endforeach()
+ endif()
foreach (test ${engine_TEST_INFILL})
add_executable(${test} tests/main.cpp tests/infill/${test}.cpp)
target_link_libraries(${test} _CuraEngine ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..ece341fbe
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Reporting vulnerabilities
+
+If you discover a vulnerability, please let us know as soon as possible via `security@ultimaker.com`.
+Please do not take advantage of the vulnerability and do not reveal the problem to others.
+To allow us to resolve the issue, please do provide us with sufficient information to reproduce the problem.
diff --git a/src/BeadingStrategy/BeadingStrategy.cpp b/src/BeadingStrategy/BeadingStrategy.cpp
index a066c2622..dde873dbd 100644
--- a/src/BeadingStrategy/BeadingStrategy.cpp
+++ b/src/BeadingStrategy/BeadingStrategy.cpp
@@ -8,14 +8,32 @@
namespace cura
{
-BeadingStrategy::BeadingStrategy(coord_t optimal_width, coord_t default_transition_length, float transitioning_angle)
- : optimal_width(optimal_width)
- , default_transition_length(default_transition_length)
- , transitioning_angle(transitioning_angle)
+BeadingStrategy::BeadingStrategy
+(
+ coord_t optimal_width,
+ Ratio wall_split_middle_threshold,
+ Ratio 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)
@@ -53,10 +71,32 @@ coord_t BeadingStrategy::getOptimalWidth() const
return optimal_width;
}
+Ratio BeadingStrategy::getSplitMiddleThreshold() const
+{
+ return wall_split_middle_threshold;
+}
+
+Ratio BeadingStrategy::getAddMiddleThreshold() const
+{
+ return wall_add_middle_threshold;
+}
+
AngleRadians 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 Ratio 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 cura
diff --git a/src/BeadingStrategy/BeadingStrategy.h b/src/BeadingStrategy/BeadingStrategy.h
index f8a366343..50c676f96 100644
--- a/src/BeadingStrategy/BeadingStrategy.h
+++ b/src/BeadingStrategy/BeadingStrategy.h
@@ -9,6 +9,7 @@
#include "../utils/IntPoint.h"
#include "../utils/logoutput.h"
#include "../settings/types/Angle.h"
+#include "../settings/types/Ratio.h" //For the wall transition threshold.
namespace cura
{
@@ -36,7 +37,16 @@ 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,
+ Ratio wall_split_middle_threshold,
+ Ratio wall_add_middle_threshold,
+ coord_t default_transition_length,
+ float transitioning_angle = pi_div(3)
+ );
+
+ BeadingStrategy(const BeadingStrategy& other);
virtual ~BeadingStrategy() {}
@@ -52,12 +62,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
@@ -89,6 +99,8 @@ public:
virtual std::string toString() const;
coord_t getOptimalWidth() const;
+ Ratio getSplitMiddleThreshold() const;
+ Ratio getAddMiddleThreshold() const;
coord_t getDefaultTransitionLength() const;
AngleRadians getTransitioningAngle() const;
@@ -97,6 +109,10 @@ protected:
coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances.
+ Ratio wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width.
+
+ Ratio 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
/*!
diff --git a/src/BeadingStrategy/BeadingStrategyFactory.cpp b/src/BeadingStrategy/BeadingStrategyFactory.cpp
index ad6e16e09..5e90b1cdb 100644
--- a/src/BeadingStrategy/BeadingStrategyFactory.cpp
+++ b/src/BeadingStrategy/BeadingStrategyFactory.cpp
@@ -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.h"
#include "LimitedBeadingStrategy.h"
-#include "CenterDeviationBeadingStrategy.h"
#include "WideningBeadingStrategy.h"
#include "DistributedBeadingStrategy.h"
#include "RedistributeBeadingStrategy.h"
@@ -15,28 +14,8 @@
namespace cura
{
-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 StrategyType type,
const coord_t preferred_bead_width_outer,
const coord_t preferred_bead_width_inner,
const coord_t preferred_transition_length,
@@ -49,48 +28,29 @@ 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 Ratio 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 StrategyType::Center:
- ret = make_unique<CenterDeviationBeadingStrategy>(bar_preferred_wall_width, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold);
- break;
- case StrategyType::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 StrategyType::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:
- logError("Cannot make strategy!\n");
- return nullptr;
- }
-
+ BeadingStrategyPtr ret = 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);
+ logDebug("Applying the Redistribute meta-strategy with outer-wall width = %d, inner-wall width = %d\n", preferred_bead_width_outer, preferred_bead_width_inner);
+ ret = make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, minimum_variable_line_ratio, move(ret));
+
if(print_thin_walls)
{
logDebug("Applying the Widening Beading meta-strategy with minimum input width %d and minimum output width %d.\n", min_feature_size, min_bead_width);
ret = make_unique<WideningBeadingStrategy>(move(ret), min_feature_size, min_bead_width);
}
- if (max_bead_count > 0)
- {
- logDebug("Applying the Redistribute meta-strategy with outer-wall width = %d, inner-wall width = %d\n", preferred_bead_width_outer, 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.
- logDebug("Applying the Limited Beading meta-strategy with maximum bead count = %d.\n", max_bead_count);
- ret = make_unique<LimitedBeadingStrategy>(max_bead_count, move(ret));
- }
-
if (outer_wall_offset > 0)
{
logDebug("Applying the OuterWallOffset meta-strategy with offset = %d.\n", outer_wall_offset);
ret = make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, move(ret));
}
+
+ //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch.
+ logDebug("Applying the Limited Beading meta-strategy with maximum bead count = %d.\n", max_bead_count);
+ ret = make_unique<LimitedBeadingStrategy>(max_bead_count, move(ret));
return ret;
}
} // namespace cura
diff --git a/src/BeadingStrategy/BeadingStrategyFactory.h b/src/BeadingStrategy/BeadingStrategyFactory.h
index ed661dd14..55cfe6d2a 100644
--- a/src/BeadingStrategy/BeadingStrategyFactory.h
+++ b/src/BeadingStrategy/BeadingStrategyFactory.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.
#ifndef BEADING_STRATEGY_FACTORY_H
@@ -10,21 +10,11 @@
namespace cura
{
-enum class StrategyType
-{
- Center,
- Distributed,
- InwardDistributed,
- None,
- COUNT
-};
-
class BeadingStrategyFactory
{
public:
static BeadingStrategyPtr makeStrategy
(
- const StrategyType type,
const coord_t preferred_bead_width_outer = MM2INT(0.5),
const coord_t preferred_bead_width_inner = MM2INT(0.5),
const coord_t preferred_transition_length = MM2INT(0.4),
@@ -37,7 +27,7 @@ public:
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
+ const Ratio minimum_variable_line_ratio = 0.5
);
};
diff --git a/src/BeadingStrategy/CenterDeviationBeadingStrategy.cpp b/src/BeadingStrategy/CenterDeviationBeadingStrategy.cpp
deleted file mode 100644
index 0f4d1ae6c..000000000
--- a/src/BeadingStrategy/CenterDeviationBeadingStrategy.cpp
+++ /dev/null
@@ -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.h"
-
-namespace cura
-{
-CenterDeviationBeadingStrategy::CenterDeviationBeadingStrategy(const coord_t pref_bead_width,
- const AngleRadians transitioning_angle,
- const Ratio wall_split_middle_threshold,
- const Ratio 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 cura
diff --git a/src/BeadingStrategy/CenterDeviationBeadingStrategy.h b/src/BeadingStrategy/CenterDeviationBeadingStrategy.h
deleted file mode 100644
index 06def9d35..000000000
--- a/src/BeadingStrategy/CenterDeviationBeadingStrategy.h
+++ /dev/null
@@ -1,50 +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 "../settings/types/Ratio.h" //For the wall transition threshold.
-#include "BeadingStrategy.h"
-#ifdef BUILD_TESTS
-#include <gtest/gtest_prod.h> //Friend tests, so that they can inspect the privates.
-#endif
-
-namespace cura
-{
-
-/*!
- * 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
-{
-#ifdef BUILD_TESTS
- FRIEND_TEST(CenterDeviationBeadingStrategy, Construction);
-#endif
-
- 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,
- AngleRadians transitioning_angle,
- Ratio wall_split_middle_threshold,
- Ratio 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 cura
-#endif // CENTER_DEVIATION_BEADING_STRATEGY_H
diff --git a/src/BeadingStrategy/DistributedBeadingStrategy.cpp b/src/BeadingStrategy/DistributedBeadingStrategy.cpp
index abd951177..b973910ac 100644
--- a/src/BeadingStrategy/DistributedBeadingStrategy.cpp
+++ b/src/BeadingStrategy/DistributedBeadingStrategy.cpp
@@ -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.h"
@@ -6,15 +6,16 @@
namespace cura
{
-DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width,
- const coord_t default_transition_length,
- const AngleRadians transitioning_angle,
- const Ratio wall_split_middle_threshold,
- const Ratio 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)
+DistributedBeadingStrategy::DistributedBeadingStrategy
+(
+ const coord_t optimal_width,
+ const coord_t default_transition_length,
+ const AngleRadians transitioning_angle,
+ const Ratio wall_split_middle_threshold,
+ const Ratio 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)
{
@@ -92,19 +93,12 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
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 cura
diff --git a/src/BeadingStrategy/DistributedBeadingStrategy.h b/src/BeadingStrategy/DistributedBeadingStrategy.h
index 50ca1eb0e..dde25da17 100644
--- a/src/BeadingStrategy/DistributedBeadingStrategy.h
+++ b/src/BeadingStrategy/DistributedBeadingStrategy.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.
#ifndef DISTRIBUTED_BEADING_STRATEGY_H
@@ -18,30 +18,25 @@ namespace cura
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.
- Ratio 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.
- Ratio 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 AngleRadians transitioning_angle,
- const Ratio wall_split_middle_threshold,
- const Ratio wall_add_middle_threshold,
- const int distribution_radius);
+ DistributedBeadingStrategy
+ (
+ const coord_t optimal_width,
+ const coord_t default_transition_length,
+ const AngleRadians transitioning_angle,
+ const Ratio wall_split_middle_threshold,
+ const Ratio wall_add_middle_threshold,
+ const int distribution_radius
+ );
virtual ~DistributedBeadingStrategy() 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;
};
diff --git a/src/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/BeadingStrategy/LimitedBeadingStrategy.cpp
index 76e20a687..48d1d6f7d 100644
--- a/src/BeadingStrategy/LimitedBeadingStrategy.cpp
+++ b/src/BeadingStrategy/LimitedBeadingStrategy.cpp
@@ -24,7 +24,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))
{
diff --git a/src/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp b/src/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
index 3221dc430..6f9af1ee6 100644
--- a/src/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
+++ b/src/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
@@ -8,9 +8,9 @@
namespace cura
{
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)
+ BeadingStrategy(*parent),
+ parent(std::move(parent)),
+ outer_wall_offset(outer_wall_offset)
{
name = "OuterWallOfsetBeadingStrategy";
}
diff --git a/src/BeadingStrategy/RedistributeBeadingStrategy.cpp b/src/BeadingStrategy/RedistributeBeadingStrategy.cpp
index 9bf461a2f..adf708895 100644
--- a/src/BeadingStrategy/RedistributeBeadingStrategy.cpp
+++ b/src/BeadingStrategy/RedistributeBeadingStrategy.cpp
@@ -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.h"
@@ -9,35 +9,48 @@
namespace cura
{
-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 Ratio 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 +71,41 @@ 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
+ // 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 = parent->compute(thickness, bead_count);
+ 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);
- }
-
- // Ensure that the positions of the beads are distributed over the thickness
- resetToolPathLocations(ret, thickness);
-
- 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)
+ // 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)
{
- if (total_outer_optimal_width + minimum_width_inner > thickness)
- {
- outer_bead_width -= minimum_width_inner / 2;
- }
- else
+ ret = parent->compute(inner_thickness, inner_bead_count);
+ for (auto& toolpath_location : ret.toolpath_locations)
{
- outer_bead_width = optimal_width_outer;
+ toolpath_location += 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)
+ // 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)
{
- beading.toolpath_locations[bead_count / 2] = thickness / 2;
+ ret.bead_widths.push_back(actual_outer_thickness);
+ ret.toolpath_locations.push_back(thickness - actual_outer_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));
+ // 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;
}
-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 cura
diff --git a/src/BeadingStrategy/RedistributeBeadingStrategy.h b/src/BeadingStrategy/RedistributeBeadingStrategy.h
index 7e1e1415e..28e767d05 100644
--- a/src/BeadingStrategy/RedistributeBeadingStrategy.h
+++ b/src/BeadingStrategy/RedistributeBeadingStrategy.h
@@ -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
@@ -32,15 +32,14 @@ namespace cura
* /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
+ (
+ const coord_t optimal_width_outer,
+ const Ratio minimum_variable_line_ratio,
+ BeadingStrategyPtr parent
+ );
virtual ~RedistributeBeadingStrategy() override = default;
@@ -92,8 +91,7 @@ namespace cura
BeadingStrategyPtr parent;
coord_t optimal_width_outer;
- coord_t optimal_width_inner;
- double minimum_variable_line_width;
+ Ratio minimum_variable_line_ratio;
};
} // namespace cura
diff --git a/src/BeadingStrategy/WideningBeadingStrategy.cpp b/src/BeadingStrategy/WideningBeadingStrategy.cpp
index cb431528c..4abcac0f9 100644
--- a/src/BeadingStrategy/WideningBeadingStrategy.cpp
+++ b/src/BeadingStrategy/WideningBeadingStrategy.cpp
@@ -7,7 +7,7 @@ namespace cura
{
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)
diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp
index 0b967cd22..ae0abfffe 100644
--- a/src/FffGcodeWriter.cpp
+++ b/src/FffGcodeWriter.cpp
@@ -762,7 +762,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage)
max_resolution, max_deviation,
wall_line_count, infill_origin, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
- VariableWidthPaths raft_paths;
+ std::vector<VariableWidthLines> raft_paths;
infill_comp.generate(raft_paths, raft_polygons, raftLines, base_settings);
if(!raft_paths.empty())
{
@@ -840,7 +840,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage)
interface_max_resolution, interface_max_deviation,
wall_line_count, infill_origin, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
- VariableWidthPaths raft_paths; //Should remain empty, since we have no walls.
+ std::vector<VariableWidthLines> raft_paths; //Should remain empty, since we have no walls.
infill_comp.generate(raft_paths, raft_polygons, raftLines, interface_settings);
gcode_layer.addLinesByOptimizer(raftLines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position);
@@ -901,7 +901,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage)
surface_max_resolution, surface_max_deviation,
wall_line_count, infill_origin, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
- VariableWidthPaths raft_paths; //Should remain empty, since we have no walls.
+ std::vector<VariableWidthLines> raft_paths; //Should remain empty, since we have no walls.
infill_comp.generate(raft_paths, raft_polygons, raft_lines, surface_settings);
gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position);
@@ -976,12 +976,13 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn
coord_t max_inner_wall_width = 0;
for (const SliceMeshStorage& mesh : storage.meshes)
{
- max_inner_wall_width = std::max(max_inner_wall_width, mesh.settings.get<coord_t>((mesh.settings.get<size_t>("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"));
- if (layer_nr == 0)
+ coord_t mesh_inner_wall_width = mesh.settings.get<coord_t>((mesh.settings.get<size_t>("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0");
+ if(layer_nr == 0)
{
const ExtruderTrain& train = mesh.settings.get<ExtruderTrain&>((mesh.settings.get<size_t>("wall_line_count") > 1) ? "wall_0_extruder_nr" : "wall_x_extruder_nr");
- max_inner_wall_width *= train.settings.get<Ratio>("initial_layer_line_width_factor");
+ mesh_inner_wall_width *= train.settings.get<Ratio>("initial_layer_line_width_factor");
}
+ max_inner_wall_width = std::max(max_inner_wall_width, mesh_inner_wall_width);
}
const coord_t comb_offset_from_outlines = max_inner_wall_width * 2;
@@ -1408,7 +1409,7 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const
part_order_optimizer.addPolygon(&part);
}
part_order_optimizer.optimize();
- for(const PathOrderOptimizer<const SliceLayerPart*>::Path& path : part_order_optimizer.paths)
+ for(const PathOrderPath<const SliceLayerPart*>& path : part_order_optimizer.paths)
{
addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer);
}
@@ -1453,7 +1454,7 @@ void FffGcodeWriter::addMeshPartToGCode(const SliceDataStorage& storage, const S
{
innermost_wall_line_width *= mesh.settings.get<Ratio>("initial_layer_line_width_factor");
}
- gcode_layer.moveInsideCombBoundary(innermost_wall_line_width);
+ gcode_layer.moveInsideCombBoundary(innermost_wall_line_width, part);
}
gcode_layer.setIsInside(false);
@@ -1503,7 +1504,7 @@ bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, La
const size_t infill_multiplier = mesh.settings.get<size_t>("infill_multiplier");
Polygons infill_polygons;
Polygons infill_lines;
- VariableWidthPaths infill_paths = part.infill_wall_toolpaths;
+ std::vector<VariableWidthLines> infill_paths = part.infill_wall_toolpaths;
for (size_t density_idx = part.infill_area_per_combine_per_density.size() - 1; (int)density_idx >= 0; density_idx--)
{ // combine different density infill areas (for gradual infill)
size_t density_factor = 2 << density_idx; // == pow(2, density_idx + 1)
@@ -1575,7 +1576,7 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L
//Combine the 1 layer thick infill with the top/bottom skin and print that as one thing.
Polygons infill_polygons;
- std::vector<VariableWidthPaths> wall_tool_paths;
+ std::vector<std::vector<VariableWidthLines>> wall_tool_paths; // All wall toolpaths binned by inset_idx (inner) and by density_idx (outer)
Polygons infill_lines;
const auto pattern = mesh.settings.get<EFillMethod>("infill_pattern");
@@ -1682,7 +1683,7 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L
// infill region with skin above has to have at least one infill wall line
const size_t min_skin_below_wall_count = wall_line_count > 0 ? wall_line_count : 1;
const size_t skin_below_wall_count = density_idx == last_idx ? min_skin_below_wall_count : 0;
- wall_tool_paths.emplace_back(VariableWidthPaths());
+ wall_tool_paths.emplace_back(std::vector<VariableWidthLines>());
const coord_t overlap = infill_overlap - (density_idx == last_idx ? 0 : wall_line_count * infill_line_width);
Infill infill_comp(pattern, zig_zaggify_infill, connect_polygons, infill_below_skin, infill_line_width,
infill_line_distance_here, overlap, infill_multiplier, infill_angle, gcode_layer.z,
@@ -1740,7 +1741,7 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L
}
wall_tool_paths.emplace_back(part.infill_wall_toolpaths); //The extra infill walls were generated separately. Add these too.
- const bool walls_generated = std::any_of(wall_tool_paths.cbegin(), wall_tool_paths.cend(), [](const VariableWidthPaths& tp){ return !tp.empty(); });
+ const bool walls_generated = std::any_of(wall_tool_paths.cbegin(), wall_tool_paths.cend(), [](const std::vector<VariableWidthLines>& tp){ return !tp.empty(); });
if(!infill_lines.empty() || !infill_polygons.empty() || walls_generated)
{
added_something = true;
@@ -1761,7 +1762,7 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L
}
else //So walls_generated must be true.
{
- VariableWidthPaths* start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()];
+ std::vector<VariableWidthLines>* start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()];
while(start_paths->empty()) //We know for sure (because walls_generated) that one of them is not empty. So randomise until we hit it. Should almost always be very quick.
{
start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()];
@@ -1771,7 +1772,7 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, L
}
if (walls_generated)
{
- for(const VariableWidthPaths& tool_paths: wall_tool_paths)
+ for(const std::vector<VariableWidthLines>& tool_paths: wall_tool_paths)
{
constexpr bool retract_before_outer_wall = false;
constexpr coord_t wipe_dist = 0;
@@ -2186,7 +2187,7 @@ bool FffGcodeWriter::processSkin(const SliceDataStorage& storage, LayerPlan& gco
}
part_order_optimizer.optimize();
- for(const PathOrderOptimizer<const SkinPart*>::Path& path : part_order_optimizer.paths)
+ for(const PathOrderPath<const SkinPart*>& path : part_order_optimizer.paths)
{
const SkinPart& skin_part = *path.vertices;
@@ -2203,10 +2204,7 @@ bool FffGcodeWriter::processSkinPart(const SliceDataStorage& storage, LayerPlan&
gcode_layer.mode_skip_agressive_merge = true;
- // add roofing
processRoofing(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part, added_something);
-
- // add normal skinfill
processTopBottom(storage, gcode_layer, mesh, extruder_nr, mesh_config, skin_part, added_something);
gcode_layer.mode_skip_agressive_merge = false;
@@ -2222,7 +2220,6 @@ void FffGcodeWriter::processRoofing(const SliceDataStorage& storage, LayerPlan&
}
const EFillMethod pattern = mesh.settings.get<EFillMethod>("roofing_pattern");
-
AngleDegrees roofing_angle = 45;
if (mesh.roofing_angles.size() > 0)
{
@@ -2404,7 +2401,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La
{
Polygons skin_polygons;
Polygons skin_lines;
- VariableWidthPaths skin_paths;
+ std::vector<VariableWidthLines> skin_paths;
constexpr int infill_multiplier = 1;
constexpr int extra_infill_shift = 0;
@@ -2414,6 +2411,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La
coord_t max_resolution = mesh.settings.get<coord_t>("meshfix_maximum_resolution");
coord_t max_deviation = mesh.settings.get<coord_t>("meshfix_maximum_deviation");
const Point infill_origin;
+ const bool skip_line_stitching = monotonic;
constexpr bool connected_zigzags = false;
constexpr bool use_endpieces = true;
constexpr bool skip_some_zags = false;
@@ -2424,6 +2422,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La
pattern, zig_zaggify_infill, connect_polygons, area, config.getLineWidth(), config.getLineWidth() / skin_density, skin_overlap, infill_multiplier, skin_angle, gcode_layer.z, extra_infill_shift
, max_resolution, max_deviation
, wall_line_count, infill_origin,
+ skip_line_stitching,
connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
infill_comp.generate(skin_paths, skin_polygons, skin_lines, mesh.settings);
@@ -2659,7 +2658,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer
constexpr bool connect_polygons = false; // polygons are too distant to connect for sparse support
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
- for(const PathOrderOptimizer<const SupportInfillPart*>::Path& path : island_order_optimizer.paths)
+ for(const PathOrderPath<const SupportInfillPart*>& path : island_order_optimizer.paths)
{
const SupportInfillPart& part = *path.vertices;
@@ -2667,7 +2666,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer
const int current_support_infill_overlap = (part.inset_count_to_generate > 0) ? default_support_infill_overlap : 0;
//The support infill walls were generated separately, first. Always add them, regardless of how many densities we have.
- VariableWidthPaths wall_toolpaths = part.wall_toolpaths;
+ std::vector<VariableWidthLines> wall_toolpaths = part.wall_toolpaths;
if ( ! wall_toolpaths.empty())
{
@@ -2691,7 +2690,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer
const coord_t support_line_width = default_support_line_width * (combine_idx + 1);
Polygons support_polygons;
- VariableWidthPaths wall_toolpaths_here;
+ std::vector<VariableWidthLines> wall_toolpaths_here;
Polygons support_lines;
const size_t max_density_idx = part.infill_area_per_combine_per_density.size() - 1;
for(size_t density_idx = max_density_idx; (density_idx + 1) > 0; --density_idx)
@@ -2721,7 +2720,6 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer
wall_count, infill_origin, support_connect_zigzags,
use_endpieces, skip_some_zags, zag_skip_count, pocket_size);
infill_comp.generate(wall_toolpaths_here, support_polygons, support_lines, infill_extruder.settings, storage.support.cross_fill_provider);
- assert(wall_toolpaths_here.empty());
}
setExtruder_addPrime(storage, gcode_layer, extruder_nr); // only switch extruder if we're sure we're going to switch
@@ -2780,6 +2778,21 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer
added_something = true;
}
+
+ //If we're printing with a support wall, that support wall generates gap filling as well.
+ //If not, the pattern may still generate gap filling (if it's connected infill or zigzag). We still want to print those.
+ if(wall_line_count == 0 && !wall_toolpaths_here.empty())
+ {
+ const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0];
+ constexpr bool retract_before_outer_wall = false;
+ constexpr coord_t wipe_dist = 0;
+ constexpr coord_t simplify_curvature = 0;
+ const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, simplify_curvature);
+ InsetOrderOptimizer wall_orderer(*this, storage, gcode_layer, infill_extruder.settings, extruder_nr,
+ config, config, config, config,
+ retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, wall_toolpaths);
+ added_something |= wall_orderer.addToLayer();
+ }
}
}
@@ -2848,7 +2861,7 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay
wall_line_count, infill_origin, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
Polygons roof_polygons;
- VariableWidthPaths roof_paths;
+ std::vector<VariableWidthLines> roof_paths;
Polygons roof_lines;
roof_computation.generate(roof_paths, roof_polygons, roof_lines, roof_extruder.settings);
if ((gcode_layer.getLayerNr() == 0 && wall.empty()) || (gcode_layer.getLayerNr() > 0 && roof_paths.empty() && roof_polygons.empty() && roof_lines.empty()))
@@ -2929,7 +2942,7 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L
wall_line_count, infill_origin, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
Polygons bottom_polygons;
- VariableWidthPaths bottom_paths;
+ std::vector<VariableWidthLines> bottom_paths;
Polygons bottom_lines;
bottom_computation.generate(bottom_paths, bottom_polygons, bottom_lines, bottom_extruder.settings);
if (bottom_paths.empty() && bottom_polygons.empty() && bottom_lines.empty())
diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp
index 11c995495..6350e7c19 100644
--- a/src/FffPolygonGenerator.cpp
+++ b/src/FffPolygonGenerator.cpp
@@ -3,6 +3,7 @@
#include <algorithm>
#include <map> // multimap (ordered map allowing duplicate keys)
+#include <numeric>
#include <fstream> // ifstream.good()
#ifdef _OPENMP
@@ -710,7 +711,7 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh)
SkinInfillAreaComputation::combineInfillLayers(mesh);
// fuzzy skin
- if (mesh.settings.get<bool>("magic_fuzzy_skin_enabled") && false) //TODO make fuzzy skin work with libArachne (CURA-7887) and then re-enable it
+ if (mesh.settings.get<bool>("magic_fuzzy_skin_enabled"))
{
processFuzzyWalls(mesh);
}
@@ -1040,76 +1041,118 @@ void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
-{//TODO make fuzzy skin work with libArachne (CURA-7887)
+{
if (mesh.settings.get<size_t>("wall_line_count") == 0)
{
return;
}
+
+ const coord_t line_width = mesh.settings.get<coord_t>("line_width");
+ const bool apply_outside_only = mesh.settings.get<bool>("magic_fuzzy_skin_outside_only");
const coord_t fuzziness = mesh.settings.get<coord_t>("magic_fuzzy_skin_thickness");
const coord_t avg_dist_between_points = mesh.settings.get<coord_t>("magic_fuzzy_skin_point_dist");
const coord_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
const coord_t range_random_point_dist = avg_dist_between_points / 2;
- unsigned int start_layer_nr = (mesh.settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::BRIM)? 1 : 0; // don't make fuzzy skin on first layer if there's a brim
+ unsigned int start_layer_nr = (mesh.settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::BRIM)? 1 : 0; // don't make fuzzy skin on first layer if there's a brim
+
+ auto hole_area = Polygons();
+ std::function<bool(const bool&, const ExtrusionJunction&)> accumulate_is_in_hole = [](const bool& prev_result, const ExtrusionJunction& junction) { return false; };
+
for (unsigned int layer_nr = start_layer_nr; layer_nr < mesh.layers.size(); layer_nr++)
{
SliceLayer& layer = mesh.layers[layer_nr];
for (SliceLayerPart& part : layer.parts)
{
- Polygons results;
-// Polygons& skin = (mesh.settings.get<ESurfaceMode>("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0]; insets no longer used in libArachne
- Polygons& skin = part.outline;
- for (PolygonRef poly : skin)
+ std::vector<VariableWidthLines> result_paths;
+ for (auto& toolpath : part.wall_toolpaths)
{
- if (mesh.settings.get<bool>("magic_fuzzy_skin_outside_only") && poly.area() < 0)
+ if (toolpath.front().inset_idx != 0)
{
- results.add(poly);
+ result_paths.push_back(toolpath);
continue;
}
- // generate points in between p0 and p1
- PolygonRef result = results.newPoly();
-
- int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point
- Point* p0 = &poly.back();
- for (Point& p1 : poly)
- { // 'a' is the (next) new point between p0 and p1
- Point p0p1 = p1 - *p0;
- int64_t p0p1_size = vSize(p0p1);
- int64_t p0pa_dist = dist_left_over;
- if (p0pa_dist >= p0p1_size)
+
+ result_paths.emplace_back();
+ auto& result_lines = result_paths.back();
+
+ if (apply_outside_only)
+ {
+ hole_area = part.print_outline.getOutsidePolygons().offset(-line_width);
+ accumulate_is_in_hole =
+ [&hole_area](const bool& prev_result, const ExtrusionJunction& junction) { return prev_result || hole_area.inside(junction.p); };
+ }
+ for (auto& line : toolpath)
+ {
+ if (apply_outside_only && std::accumulate(line.begin(), line.end(), false, accumulate_is_in_hole))
{
- result.add(p1 - (p0p1 / 2));
+ result_lines.push_back(line);
+ continue;
}
- for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
+
+ result_lines.emplace_back();
+ auto& result = result_lines.back();
+ result.inset_idx = line.inset_idx;
+
+ // generate points in between p0 and p1
+ int64_t dist_left_over = (min_dist_between_points / 4) + rand() % (min_dist_between_points / 4); // the distance to be traversed on the line before making the first new point
+ auto* p0 = &line.front();
+ for (auto& p1 : line)
{
- int r = rand() % (fuzziness * 2) - fuzziness;
- Point perp_to_p0p1 = turn90CCW(p0p1);
- Point fuzz = normal(perp_to_p0p1, r);
- Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
- result.add(pa);
- }
- // p0pa_dist > p0p1_size now because we broke out of the for-loop
- dist_left_over = p0pa_dist - p0p1_size;
+ if (p0->p == p1.p) // avoid seams
+ {
+ result.emplace_back(p1.p, p1.w, p1.perimeter_index);
+ continue;
+ }
- p0 = &p1;
- }
- while (result.size() < 3)
- {
- size_t point_idx = poly.size() - 2;
- result.add(poly[point_idx]);
- if (point_idx == 0)
+ // 'a' is the (next) new point between p0 and p1
+ const Point p0p1 = p1.p - p0->p;
+ const int64_t p0p1_size = vSize(p0p1);
+ int64_t p0pa_dist = dist_left_over;
+ if (p0pa_dist >= p0p1_size)
+ {
+ const Point p = p1.p - (p0p1 / 2);
+ const double width = (p1.w * vSize(p1.p - p) + p0->w * vSize(p0->p - p)) / p0p1_size;
+ result.emplace_back(p, width, p1.perimeter_index);
+ }
+ for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
+ {
+ const int r = rand() % (fuzziness * 2) - fuzziness;
+ const Point perp_to_p0p1 = turn90CCW(p0p1);
+ const Point fuzz = normal(perp_to_p0p1, r);
+ const Point pa = p0->p + normal(p0p1, p0pa_dist);
+ const double width = (p1.w * vSize(p1.p - pa) + p0->w * vSize(p0->p - pa)) / p0p1_size;
+ result.emplace_back(pa + fuzz, width, p1.perimeter_index);
+ }
+ // p0pa_dist > p0p1_size now because we broke out of the for-loop
+ dist_left_over = p0pa_dist - p0p1_size;
+
+ p0 = &p1;
+ }
+ while (result.size() < 3)
+ {
+ size_t point_idx = line.size() - 2;
+ result.emplace_back(line[point_idx].p, line[point_idx].w, line[point_idx].perimeter_index);
+ if (point_idx == 0)
+ {
+ break;
+ }
+ point_idx--;
+ }
+ if (result.size() < 3)
{
- break;
+ result.clear();
+ for (auto& p : line)
+ {
+ result.emplace_back(p.p, p.w, p.perimeter_index);
+ }
+ }
+ if (line.back().p == line.front().p) // avoid seams
+ {
+ result.back().p = result.front().p;
}
- point_idx--;
- }
- if (result.size() < 3)
- {
- result.clear();
- for (Point& p : poly)
- result.add(p);
}
}
- skin = results;
+ part.wall_toolpaths = result_paths;
}
}
}
diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp
index e51f349f0..f606c13bb 100644
--- a/src/InsetOrderOptimizer.cpp
+++ b/src/InsetOrderOptimizer.cpp
@@ -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 "ExtruderTrain.h"
@@ -26,7 +26,7 @@ InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer,
const size_t wall_0_extruder_nr,
const size_t wall_x_extruder_nr,
const ZSeamConfig& z_seam_config,
- const VariableWidthPaths& paths) :
+ const std::vector<VariableWidthLines>& paths) :
gcode_writer(gcode_writer),
storage(storage),
gcode_layer(gcode_layer),
@@ -54,7 +54,6 @@ bool InsetOrderOptimizer::addToLayer()
// Settings & configs:
const bool pack_by_inset = ! settings.get<bool>("optimize_wall_printing_order");
const InsetDirection inset_direction = settings.get<InsetDirection>("inset_direction");
- const bool center_last = inset_direction == InsetDirection::CENTER_LAST;
const bool alternate_walls = settings.get<bool>("material_alternate_walls");
const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN;
@@ -129,23 +128,6 @@ bool InsetOrderOptimizer::addToLayer()
getInsetOrder(walls_to_be_added, outer_to_inner)
: getRegionOrder(walls_to_be_added, outer_to_inner);
- if (center_last)
- {
- for (const ExtrusionLine* line : walls_to_be_added)
- {
- if (line->is_odd)
- {
- for (const ExtrusionLine* other_line : walls_to_be_added)
- {
- if ( ! other_line->is_odd)
- {
- order.emplace(std::make_pair(other_line, line));
- }
- }
- }
- }
- }
-
constexpr Ratio flow = 1.0_r;
bool added_something = false;
@@ -173,7 +155,7 @@ bool InsetOrderOptimizer::addToLayer()
order_optimizer.optimize();
cura::Point p_end {0, 0};
- for(const PathOrderOptimizer<const ExtrusionLine*>::Path& path : order_optimizer.paths)
+ for(const PathOrderPath<const ExtrusionLine*>& path : order_optimizer.paths)
{
if (path.vertices->empty()) continue;
@@ -354,7 +336,6 @@ std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> InsetO
{
for (const ExtrusionLine* line : fillers_by_inset[inset_idx])
{
- assert(inset_idx - 1 < walls_by_inset.size());
if (inset_idx - 1 >= walls_by_inset.size()) continue;
for (const ExtrusionLine* enclosing_wall : walls_by_inset[inset_idx - 1])
{
diff --git a/src/InsetOrderOptimizer.h b/src/InsetOrderOptimizer.h
index 23ed9a8ea..d8aa87d48 100644
--- a/src/InsetOrderOptimizer.h
+++ b/src/InsetOrderOptimizer.h
@@ -51,7 +51,7 @@ public:
const size_t wall_0_extruder_nr,
const size_t wall_x_extruder_nr,
const ZSeamConfig& z_seam_config,
- const VariableWidthPaths& paths);
+ const std::vector<VariableWidthLines>& paths);
/*!
* Adds the insets to the given layer plan.
@@ -104,7 +104,7 @@ private:
const size_t wall_0_extruder_nr;
const size_t wall_x_extruder_nr;
const ZSeamConfig& z_seam_config;
- const VariableWidthPaths& paths;
+ const std::vector<VariableWidthLines>& paths;
const unsigned int layer_nr;
bool added_something;
diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp
index 89de2cfd5..74b946293 100644
--- a/src/LayerPlan.cpp
+++ b/src/LayerPlan.cpp
@@ -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 <cstring>
@@ -85,25 +85,24 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe
constexpr double epsilon_speed_factor = 0.001; // Don't put on actual 'limit double minimum', because we don't want printers to stall.
for (auto& path : paths)
{
- const Ratio nominal_flow_for_path = path.config->getFlowRatio();
const double nominal_width_for_path = static_cast<double>(path.config->getLineWidth());
- if (path.flow <= 0.0 || nominal_flow_for_path <= 0.0 || nominal_width_for_path <= 0.0 || path.config->isTravelPath() || path.config->isBridgePath())
+ if(path.width_factor <= 0.0 || nominal_width_for_path <= 0.0 || path.config->isTravelPath() || path.config->isBridgePath())
{
continue;
}
- const double line_width_for_path = path.flow * nominal_flow_for_path * nominal_width_for_path;
+ const double line_width_for_path = path.width_factor * nominal_width_for_path;
path.speed_back_pressure_factor = std::max(epsilon_speed_factor, 1.0 + (nominal_width_for_path / line_width_for_path - 1.0) * back_pressure_compensation);
}
}
-GCodePath* LayerPlan::getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow, bool spiralize, const Ratio speed_factor)
+GCodePath* LayerPlan::getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, bool spiralize, const Ratio speed_factor)
{
std::vector<GCodePath>& paths = extruder_plans.back().paths;
- if (paths.size() > 0 && paths.back().config == &config && !paths.back().done && paths.back().flow == flow && paths.back().speed_factor == speed_factor && paths.back().mesh_id == current_mesh) // spiralize can only change when a travel path is in between
+ if(paths.size() > 0 && paths.back().config == &config && !paths.back().done && paths.back().flow == flow && paths.back().width_factor == width_factor && paths.back().speed_factor == speed_factor && paths.back().mesh_id == current_mesh) // spiralize can only change when a travel path is in between
{
return &paths.back();
}
- paths.emplace_back(config, current_mesh, space_fill_type, flow, spiralize, speed_factor);
+ paths.emplace_back(config, current_mesh, space_fill_type, flow, width_factor, spiralize, speed_factor);
GCodePath* ret = &paths.back();
ret->skip_agressive_merge_hint = mode_skip_agressive_merge;
return ret;
@@ -301,16 +300,17 @@ void LayerPlan::setMesh(const std::string mesh_id)
current_mesh = mesh_id;
}
-void LayerPlan::moveInsideCombBoundary(const coord_t distance)
+void LayerPlan::moveInsideCombBoundary(const coord_t distance, const std::optional<SliceLayerPart>& part)
{
constexpr coord_t max_dist2 = MM2INT(2.0) * MM2INT(2.0); // if we are further than this distance, we conclude we are not inside even though we thought we were.
- // this function is to be used to move from the boudary of a part to inside the part
+ // this function is to be used to move from the boundary of a part to inside the part
Point p = getLastPlannedPositionOrStartingPosition(); // copy, since we are going to move p
if (PolygonUtils::moveInside(comb_boundary_preferred, p, distance, max_dist2) != NO_INDEX)
{
//Move inside again, so we move out of tight 90deg corners
PolygonUtils::moveInside(comb_boundary_preferred, p, distance, max_dist2);
- if (comb_boundary_preferred.inside(p))
+ if (comb_boundary_preferred.inside(p) &&
+ (part == std::nullopt || part->outline.inside(p)))
{
addTravel_simple(p);
//Make sure the that any retraction happens after this move, not before it by starting a new move path.
@@ -510,9 +510,9 @@ void LayerPlan::planPrime(const float& prime_blob_wipe_length)
forceNewPathStart();
}
-void LayerPlan::addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow, bool spiralize, Ratio speed_factor, double fan_speed)
+void LayerPlan::addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, bool spiralize, Ratio speed_factor, double fan_speed)
{
- GCodePath* path = getLatestPathWithConfig(config, space_fill_type, flow, spiralize, speed_factor);
+ GCodePath* path = getLatestPathWithConfig(config, space_fill_type, flow, width_factor, spiralize, speed_factor);
path->points.push_back(p);
path->setFanSpeed(fan_speed);
last_planned_position = p;
@@ -520,19 +520,20 @@ void LayerPlan::addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFi
void LayerPlan::addPolygon(ConstPolygonRef polygon, int start_idx, const bool backwards, const GCodePathConfig& config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio& flow_ratio, bool always_retract)
{
+ constexpr Ratio width_ratio = 1.0_r; //Not printed with variable line width.
Point p0 = polygon[start_idx];
addTravel(p0, always_retract);
const int direction = backwards ? -1 : 1;
for(size_t point_idx = 1; point_idx < polygon.size(); point_idx++)
{
Point p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()];
- addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, spiralize);
+ addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize);
p0 = p1;
}
if(polygon.size() > 2)
{
const Point& p1 = polygon[start_idx];
- addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, spiralize);
+ addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize);
if(wall_0_wipe_dist > 0)
{ // apply outer wall wipe
@@ -580,7 +581,7 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath
if(!reverse_order)
{
- for(const PathOrderOptimizer<ConstPolygonPointer>::Path& path : orderOptimizer.paths)
+ for(const PathOrderPath<ConstPolygonPointer>& path : orderOptimizer.paths)
{
addPolygon(*path.vertices, path.start_vertex, path.backwards, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract);
}
@@ -589,7 +590,7 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath
{
for(int index = orderOptimizer.paths.size() - 1; index >= 0; --index)
{
- const PathOrderOptimizer<ConstPolygonPointer>::Path& path = orderOptimizer.paths[index];
+ const PathOrderPath<ConstPolygonPointer>& path = orderOptimizer.paths[index];
addPolygon(**path.vertices, path.start_vertex, path.backwards, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract);
}
}
@@ -597,7 +598,7 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, const GCodePath
static constexpr float max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate
-void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, float flow, float& non_bridge_line_volume, Ratio speed_factor, double distance_to_bridge_start)
+void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, float flow, const Ratio width_factor, float& non_bridge_line_volume, Ratio speed_factor, double distance_to_bridge_start)
{
const coord_t min_line_len = 5; // we ignore lines less than 5um long
const double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length
@@ -652,15 +653,16 @@ void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& se
if ((len - coast_dist) > min_line_len)
{
// segment is longer than coast distance so extrude using non-bridge config to start of coast
- addExtrusionMove(segment_end + coast_dist * (cur_point - segment_end) / len, non_bridge_config, SpaceFillType::Polygons, segment_flow, spiralize, speed_factor);
+ addExtrusionMove(segment_end + coast_dist * (cur_point - segment_end) / len, non_bridge_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, speed_factor);
}
// then coast to start of bridge segment
- addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, 0, spiralize, speed_factor);
+ constexpr Ratio flow = 0.0_r; //Coasting has no flow rate.
+ addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, speed_factor);
}
else
{
// no coasting required, just normal segment using non-bridge config
- addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, segment_flow, spiralize,
+ addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize,
(overhang_mask.empty() || (!overhang_mask.inside(p0, true) && !overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor);
}
@@ -669,10 +671,10 @@ void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& se
else
{
// no coasting required, just normal segment using non-bridge config
- addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, segment_flow, spiralize,
+ addExtrusionMove(segment_end, non_bridge_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize,
(overhang_mask.empty() || (!overhang_mask.inside(p0, true) && !overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor);
}
- non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * speed_factor * non_bridge_config.getSpeed();
+ non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * width_factor * speed_factor * non_bridge_config.getSpeed();
cur_point = segment_end;
speed_factor = 1 - (1 - speed_factor) * acceleration_factor;
if (speed_factor >= 0.9)
@@ -686,7 +688,7 @@ void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& se
if (bridge_wall_mask.empty())
{
// no bridges required
- addExtrusionMove(p1, non_bridge_config, SpaceFillType::Polygons, flow, spiralize,
+ addExtrusionMove(p1, non_bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize,
(overhang_mask.empty() || (!overhang_mask.inside(p0, true) && !overhang_mask.inside(p1, true))) ? 1.0_r : overhang_speed_factor);
}
else
@@ -744,7 +746,7 @@ void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& se
if (bridge_line_len > min_line_len)
{
- addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow);
+ addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor);
non_bridge_line_volume = 0;
cur_point = b1;
// after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag
@@ -768,7 +770,7 @@ void LayerPlan::addWallLine(const Point& p0, const Point& p1, const Settings& se
else if (bridge_wall_mask.inside(p0, true) && vSize(p0 - p1) >= min_bridge_line_len)
{
// both p0 and p1 must be above air (the result will be ugly!)
- addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow);
+ addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor);
non_bridge_line_volume = 0;
}
else
@@ -968,12 +970,12 @@ void LayerPlan::addWall(const ExtrusionLine& wall, int start_idx, const Settings
if(is_small_feature)
{
constexpr bool spiralize = false;
- addExtrusionMove(destination, non_bridge_config, SpaceFillType::Polygons, flow_ratio * (line_width * nominal_line_width_multiplier), spiralize, small_feature_speed_factor);
+ addExtrusionMove(destination, non_bridge_config, SpaceFillType::Polygons, flow_ratio, line_width * nominal_line_width_multiplier, spiralize, small_feature_speed_factor);
}
else
{
const Point origin = p0.p + normal(line_vector, piece_length * piece);
- addWallLine(origin, destination, settings, non_bridge_config, bridge_config, flow_ratio * (line_width * nominal_line_width_multiplier), non_bridge_line_volume, speed_factor, distance_to_bridge_start);
+ addWallLine(origin, destination, settings, non_bridge_config, bridge_config, flow_ratio, line_width * nominal_line_width_multiplier, non_bridge_line_volume, speed_factor, distance_to_bridge_start);
}
}
@@ -1031,9 +1033,10 @@ void LayerPlan::addInfillWall(const ExtrusionLine& wall, const GCodePathConfig&
for (const auto &junction_n : wall)
{
- const double flow = junction_n.w / Ratio(path_config.getLineWidth());
+ const Ratio width_factor = junction_n.w / Ratio(path_config.getLineWidth());
constexpr SpaceFillType space_fill_type = SpaceFillType::Polygons;
- addExtrusionMove(junction_n.p, path_config, space_fill_type, flow);
+ constexpr Ratio flow = 1.0_r;
+ addExtrusionMove(junction_n.p, path_config, space_fill_type, flow, width_factor);
junction = junction_n;
}
}
@@ -1057,7 +1060,7 @@ void LayerPlan::addWalls
orderOptimizer.addPolygon(walls[poly_idx]);
}
orderOptimizer.optimize();
- for(const PathOrderOptimizer<ConstPolygonPointer>::Path& path : orderOptimizer.paths)
+ for(const PathOrderPath<ConstPolygonPointer>& path : orderOptimizer.paths)
{
addWall(**path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract);
}
@@ -1106,12 +1109,25 @@ void LayerPlan::addLinesByOptimizer
order_optimizer.addPolyline(polygons[line_idx]);
}
order_optimizer.optimize();
+
+ addLinesInGivenOrder(order_optimizer.paths, config, space_fill_type, wipe_dist, flow_ratio, fan_speed);
+}
+
+void LayerPlan::addLinesInGivenOrder(
+ const std::vector<PathOrderPath<ConstPolygonPointer>>& paths,
+ const GCodePathConfig& config,
+ const SpaceFillType space_fill_type,
+ const coord_t wipe_dist,
+ const Ratio flow_ratio,
+ const double fan_speed
+)
+{
coord_t half_line_width = config.getLineWidth() / 2;
coord_t line_width_2 = half_line_width * half_line_width;
- for(size_t order_idx = 0; order_idx < order_optimizer.paths.size(); order_idx++)
+ for(size_t order_idx = 0; order_idx < paths.size(); order_idx++)
{
- const PathOrderOptimizer<ConstPolygonPointer>::Path& path = order_optimizer.paths[order_idx];
+ const PathOrderPath<ConstPolygonPointer>& path = paths[order_idx];
ConstPolygonRef polyline = *path.vertices;
const size_t start_idx = path.start_vertex;
assert(start_idx == 0 || start_idx == polyline.size() - 1 || path.is_closed);
@@ -1121,7 +1137,11 @@ void LayerPlan::addLinesByOptimizer
{
// Instead of doing a small travel that is shorter than the line width (which is generally done at pretty high jerk & move) do a
// "fake" extrusion move
- addExtrusionMove(start, config, space_fill_type, 0, false, 1.0, fan_speed);
+ constexpr Ratio flow = 0.0_r;
+ constexpr Ratio width_factor = 1.0_r;
+ constexpr bool spiralize = false;
+ constexpr Ratio speed_factor = 1.0_r;
+ addExtrusionMove(start, config, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed);
}
else
{
@@ -1150,13 +1170,16 @@ void LayerPlan::addLinesByOptimizer
// ignore line segments that are less than 5uM long
if (vSize2(p1 - p0) >= MINIMUM_SQUARED_LINE_LENGTH)
{
- addExtrusionMove(p1, config, space_fill_type, flow_ratio, false, 1.0, fan_speed);
+ constexpr Ratio width_factor = 1.0_r;
+ constexpr bool spiralize = false;
+ constexpr Ratio speed_factor = 1.0_r;
+ addExtrusionMove(p1, config, space_fill_type, flow_ratio, width_factor, spiralize, speed_factor, fan_speed);
p0 = p1;
}
}
- p0 = polyline[(start_idx == 0) ? polyline.size() - 1 : 0];
- Point p1 = (polyline.size() <= 1) ? p0
+ Point p1 = polyline[(start_idx == 0) ? polyline.size() - 1 : 0];
+ p0 = (polyline.size() <= 1) ? p1
: polyline[(start_idx == 0) ? polyline.size() - 2 : 1];
// Wipe
@@ -1172,9 +1195,9 @@ void LayerPlan::addLinesByOptimizer
}
// Don't wipe if next starting point is very near
- if(wipe && (order_idx < order_optimizer.paths.size() - 1))
+ if(wipe && (order_idx < paths.size() - 1))
{
- const PathOrderOptimizer<ConstPolygonPointer>::Path& next_path = order_optimizer.paths[order_idx + 1];
+ const PathOrderPath<ConstPolygonPointer>& next_path = paths[order_idx + 1];
ConstPolygonRef next_polygon = *next_path.vertices;
const size_t next_start = next_path.start_vertex;
const Point& next_p0 = next_polygon[next_start];
@@ -1186,7 +1209,11 @@ void LayerPlan::addLinesByOptimizer
if (wipe)
{
- addExtrusionMove(p1 + normal(p1 - p0, wipe_dist), config, space_fill_type, 0.0, false, 1.0, fan_speed);
+ constexpr Ratio flow = 0.0_r;
+ constexpr Ratio width_factor = 1.0_r;
+ constexpr bool spiralize = false;
+ constexpr Ratio speed_factor = 1.0_r;
+ addExtrusionMove(p1 + normal(p1 - p0, wipe_dist), config, space_fill_type, flow, width_factor, spiralize, speed_factor, fan_speed);
}
}
}
@@ -1225,7 +1252,7 @@ void LayerPlan::addLinesMonotonic
};
// Order monotonically, except for line-segments which stay in the excluded areas (read: close to the walls) consecutively.
- PathOrderMonotonic<ConstPolygonRef> order(monotonic_direction, max_adjacent_distance, last_position);
+ PathOrderMonotonic<ConstPolygonPointer> order(monotonic_direction, max_adjacent_distance, last_position);
Polygons left_over;
bool last_would_have_been_excluded = false;
for(size_t line_idx = 0; line_idx < line_order.paths.size(); ++line_idx)
@@ -1246,74 +1273,17 @@ void LayerPlan::addLinesMonotonic
order.optimize();
// Read out and process the monotonically ordered lines.
- Point current_last_position = last_position;
- coord_t half_line_width = config.getLineWidth() / 2;
- coord_t line_width_2 = half_line_width * half_line_width;
- for (unsigned int order_idx = 0; order_idx < order.paths.size(); order_idx++)
- {
- const PathOrder<ConstPolygonRef>::Path& path = order.paths[order_idx];
- ConstPolygonRef polygon = *path.vertices;
- const size_t start = path.start_vertex;
- const size_t end = path.vertices.size() - 1 - start;
- const Point& p0 = polygon[start];
- const Point& p1 = polygon[end];
- // ignore line segments that are less than 5uM long
- if (vSize2(p1 - p0) < MINIMUM_SQUARED_LINE_LENGTH)
- {
- continue;
- }
- if(vSize2(current_last_position - p0) < line_width_2)
- {
- // Instead of doing a small travel that is shorter than the line width (which is generally done at pretty high jerk & move) do a
- // "fake" extrusion move
- addExtrusionMove(p0, config, space_fill_type, 0, false, 1.0, fan_speed);
- }
- else
- {
- addTravel(p0);
- }
- addExtrusionMove(p1, config, space_fill_type, flow_ratio, false, 1.0, fan_speed);
- current_last_position = p1;
-
- // Wipe
- if (wipe_dist != 0)
- {
- bool wipe = true;
- int line_width = config.getLineWidth();
-
- // Don't wipe is current extrusion is too small
- if (vSize2(p1 - p0) <= line_width * line_width * 4)
- {
- wipe = false;
- }
-
- // Don't wipe if next starting point is very near
- if (wipe && (order_idx < order.paths.size() - 1))
- {
- const PathOrder<ConstPolygonRef>::Path& next_path = order.paths[order_idx + 1];
- ConstPolygonRef next_polygon = *next_path.vertices;
- const size_t next_start = next_path.start_vertex;
- const Point& next_p0 = next_polygon[next_start];
- if (vSize2(next_p0 - p1) <= line_width * line_width * 4)
- {
- wipe = false;
- }
- }
-
- if(wipe)
- {
- addExtrusionMove(p1 + normal(p1 - p0, wipe_dist), config, space_fill_type, 0.0, false, 1.0, fan_speed);
- }
- }
- }
+ addLinesInGivenOrder(order.paths, config, space_fill_type, wipe_dist, flow_ratio, fan_speed);
// Add all lines in the excluded areas the 'normal' way.
- addLinesByOptimizer(left_over, config, space_fill_type, true, wipe_dist, flow_ratio, current_last_position, fan_speed);
+ addLinesByOptimizer(left_over, config, space_fill_type, true, wipe_dist, flow_ratio, getLastPlannedPositionOrStartingPosition(), fan_speed);
}
void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRef wall, ConstPolygonRef last_wall, const int seam_vertex_idx, const int last_seam_vertex_idx, const bool is_top_layer, const bool is_bottom_layer)
{
const bool smooth_contours = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<bool>("smooth_spiralized_contours");
+ constexpr bool spiralize = true; //In addExtrusionMove calls, enable spiralize and use nominal line width.
+ constexpr Ratio width_factor = 1.0_r;
// once we are into the spiral we always start at the end point of the last layer (if any)
const Point origin = (last_seam_vertex_idx >= 0 && !is_bottom_layer) ? last_wall[last_seam_vertex_idx] : wall[seam_vertex_idx];
@@ -1326,7 +1296,8 @@ void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRe
Point join_first_wall_at = LinearAlg2D::getClosestOnLineSegment(origin, wall[seam_vertex_idx], wall[(seam_vertex_idx + 1) % wall.size()]);
if (vSize(join_first_wall_at - origin) > 10)
{
- addExtrusionMove(join_first_wall_at, config, SpaceFillType::Polygons, 1.0, true);
+ constexpr Ratio flow = 1.0_r;
+ addExtrusionMove(join_first_wall_at, config, SpaceFillType::Polygons, flow, width_factor, spiralize);
}
}
@@ -1409,18 +1380,18 @@ void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRe
if (cpp.isValid() && vSize2(cpp.location - p) <= max_dist2)
{
// interpolate between cpp.location and p depending on how far we have progressed along wall
- addExtrusionMove(cpp.location + (p - cpp.location) * (wall_length / total_length), config, SpaceFillType::Polygons, flow, true, speed_factor);
+ addExtrusionMove(cpp.location + (p - cpp.location) * (wall_length / total_length), config, SpaceFillType::Polygons, flow, width_factor, spiralize, speed_factor);
}
else
{
// no point in the last wall was found close enough to the current wall point so don't interpolate
- addExtrusionMove(p, config, SpaceFillType::Polygons, flow, true, speed_factor);
+ addExtrusionMove(p, config, SpaceFillType::Polygons, flow, width_factor, spiralize, speed_factor);
}
}
else
{
// no smoothing, use point verbatim
- addExtrusionMove(p, config, SpaceFillType::Polygons, flow, true, speed_factor);
+ addExtrusionMove(p, config, SpaceFillType::Polygons, flow, width_factor, spiralize, speed_factor);
}
}
@@ -1444,7 +1415,8 @@ void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRe
distance_coasted += seg_length;
}
// reduce number of paths created when polygon has many points by limiting precision of flow
- addExtrusionMove(p, config, SpaceFillType::Polygons, ((int)(flow * 20)) / 20.0, false, speed_factor);
+ constexpr bool no_spiralize = false;
+ addExtrusionMove(p, config, SpaceFillType::Polygons, ((int)(flow * 20)) / 20.0, width_factor, no_spiralize, speed_factor);
}
}
}
@@ -1691,11 +1663,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode)
const ExtruderTrain& extruder = Application::getInstance().current_slice->scene.extruders[extruder_nr];
- { // require printing temperature to be met
- constexpr bool wait = true;
- gcode.writeTemperatureCommand(extruder_nr, extruder_plan.required_start_temperature, wait);
- }
-
if (extruder_plan.prev_extruder_standby_temp)
{ // turn off previous extruder
constexpr bool wait = false;
@@ -1708,6 +1675,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode)
gcode.writeTemperatureCommand(prev_extruder, prev_extruder_temp, wait);
}
+ { // require printing temperature to be met
+ constexpr bool wait = true;
+ gcode.writeTemperatureCommand(extruder_nr, extruder_plan.required_start_temperature, wait);
+ }
+
const double extra_prime_amount = extruder.settings.get<bool>("retraction_enable") ? extruder.settings.get<double>("switch_extruder_extra_prime_amount") : 0;
gcode.addExtraPrimeAmount(extra_prime_amount);
}
diff --git a/src/LayerPlan.h b/src/LayerPlan.h
index ca5006086..6a0619900 100644
--- a/src/LayerPlan.h
+++ b/src/LayerPlan.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.
#ifndef LAYER_PLAN_H
@@ -283,7 +283,7 @@ private:
* \param speed_factor (optional) a factor which the speed will be multiplied by.
* \return A path with the given config which is now the last path in LayerPlan::paths
*/
- GCodePath* getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow = 1.0_r, bool spiralize = false, const Ratio speed_factor = 1.0_r);
+ GCodePath* getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow = 1.0_r, const Ratio width_factor = 1.0_r, bool spiralize = false, const Ratio speed_factor = 1.0_r);
public:
/*!
@@ -456,13 +456,20 @@ public:
*
* \param p The point to extrude to
* \param config The config with which to extrude
- * \param space_fill_type Of what space filling type this extrusion move is a part
- * \param flow A modifier of the extrusion width which would follow from the \p config
- * \param speed_factor (optional) A factor the travel speed will be multipled by.
- * \param spiralize Whether to gradually increase the z while printing. (Note that this path may be part of a sequence of spiralized paths, forming one polygon)
- * \param fan_speed fan speed override for this path
+ * \param space_fill_type Of what space filling type this extrusion move is
+ * a part.
+ * \param flow A modifier of the flow rate which would follow from the
+ * \p config.
+ * \param width_factor A modifier of the line width which would follow from
+ * the \p config.
+ * \param speed_factor (optional) A factor the travel speed will be
+ * multiplied by.
+ * \param spiralize Whether to gradually increase the z while printing.
+ * (Note that this path may be part of a sequence of spiralized paths,
+ * forming one polygon.)
+ * \param fan_speed Fan speed override for this path.
*/
- void addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, bool spiralize = false, Ratio speed_factor = 1.0_r, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT);
+ void addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, const Ratio width_factor = 1.0_r, bool spiralize = false, Ratio speed_factor = 1.0_r, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT);
/*!
* Add polygon to the gcode starting at vertex \p startIdx
@@ -509,18 +516,25 @@ public:
/*!
* Add a single line that is part of a wall to the gcode.
- * \param p0 The start vertex of the line
- * \param p1 The end vertex of the line
- * \param settings The settings which should apply to this line added to the layer plan.
- * \param non_bridge_config The config with which to print the wall lines that are not spanning a bridge
- * \param bridge_config The config with which to print the wall lines that are spanning a bridge
- * \param flow The ratio with which to multiply the extrusion amount
- * \param non_bridge_line_volume A pseudo-volume that is derived from the print speed and flow of the non-bridge lines that have preceeded this line
- * \param speed_factor This modifies the print speed when accelerating after a bridge line
- * \param distance_to_bridge_start The distance along the wall from p0 to the first bridge segment
+ * \param p0 The start vertex of the line.
+ * \param p1 The end vertex of the line.
+ * \param settings The settings which should apply to this line added to the
+ * layer plan.
+ * \param non_bridge_config The config with which to print the wall lines
+ * that are not spanning a bridge.
+ * \param bridge_config The config with which to print the wall lines that
+ * are spanning a bridge.
+ * \param flow The ratio with which to multiply the extrusion amount.
+ * \param width_ratio The ratio with which to multiply the line width.
+ * \param non_bridge_line_volume A pseudo-volume that is derived from the
+ * print speed and flow of the non-bridge lines that have preceded this
+ * line.
+ * \param speed_factor This modifies the print speed when accelerating after
+ * a bridge line.
+ * \param distance_to_bridge_start The distance along the wall from p0 to
+ * the first bridge segment.
*/
-
- void addWallLine(const Point& p0, const Point& p1, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, float flow, float& non_bridge_line_volume, Ratio speed_factor, double distance_to_bridge_start);
+ void addWallLine(const Point& p0, const Point& p1, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, float flow, const Ratio width_factor, float& non_bridge_line_volume, Ratio speed_factor, double distance_to_bridge_start);
/*!
* Add a wall to the g-code starting at vertex \p start_idx
@@ -640,6 +654,26 @@ public:
const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT
);
+protected:
+ /*!
+ * Add order optimized lines to the gcode.
+ * \param paths The paths in order
+ * \param config The config of the lines
+ * \param space_fill_type The type of space filling used to generate the line segments (should be either Lines or PolyLines!)
+ * \param wipe_dist (optional) the distance wiped without extruding after laying down a line.
+ * \param flow_ratio The ratio with which to multiply the extrusion amount
+ * \param fan_speed optional fan speed override for this path
+ */
+ void addLinesInGivenOrder(
+ const std::vector<PathOrderPath<ConstPolygonPointer>>& paths,
+ const GCodePathConfig& config,
+ const SpaceFillType space_fill_type,
+ const coord_t wipe_dist,
+ const Ratio flow_ratio,
+ const double fan_speed
+ );
+
+public:
/*!
* Add a spiralized slice of wall that is interpolated in X/Y between \p last_wall and \p wall.
*
@@ -738,12 +772,17 @@ public:
void processFanSpeedAndMinimalLayerTime(Point starting_position);
/*!
- * Add a travel move to the layer plan to move inside the current layer part by a given distance away from the outline.
- * This is supposed to be called when the nozzle is around the boundary of a layer part, not when the nozzle is in the middle of support, or in the middle of the air.
- *
- * \param distance The distance to the comb boundary after we moved inside it.
- */
- void moveInsideCombBoundary(const coord_t distance);
+ * Add a travel move to the layer plan to move inside the current layer part
+ * by a given distance away from the outline.
+ *
+ * This is supposed to be called when the nozzle is around the boundary of a
+ * layer part, not when the nozzle is in the middle of support, or in the
+ * middle of the air.
+ * \param distance The distance to the comb boundary after we moved inside
+ * it.
+ * \param part If given, stay within the boundary of this part.
+ */
+ void moveInsideCombBoundary(const coord_t distance, const std::optional<SliceLayerPart>& part = std::nullopt);
/*!
* Apply back-pressure compensation to this layer-plan.
diff --git a/src/PathOrder.cpp b/src/PathOrder.cpp
deleted file mode 100644
index 5974a17ca..000000000
--- a/src/PathOrder.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-//Copyright (c) 2021 Ultimaker B.V.
-//CuraEngine is released under the terms of the AGPLv3 or higher.
-
-#include "PathOrder.h" //The definitions we're implementing here.
-#include "sliceDataStorage.h" //For SliceLayerPart and related parts to reorder.
-
-//Since the PathOrder is a template class, we will only implement the template specializations in this file.
-//The templated segments need to go into the header.
-
-namespace cura
-{
-
-template<>
-ConstPolygonRef PathOrder<ConstPolygonRef>::getVertexData(ConstPolygonRef path)
-{
- return path;
-}
-
-template<>
-ConstPolygonRef PathOrder<PolygonRef>::getVertexData(PolygonRef path)
-{
- return path;
-}
-
-template<>
-ConstPolygonRef PathOrder<const SkinPart*>::getVertexData(const SkinPart* path)
-{
- return path->outline.outerPolygon();
-}
-
-template<>
-ConstPolygonRef PathOrder<const SliceLayerPart*>::getVertexData(const SliceLayerPart* path)
-{
- return path->outline.outerPolygon();
-}
-
-template<>
-ConstPolygonRef PathOrder<const SupportInfillPart*>::getVertexData(const SupportInfillPart* path)
-{
- return path->outline.outerPolygon();
-}
-
-} \ No newline at end of file
diff --git a/src/PathOrder.h b/src/PathOrder.h
index 2c11bb824..f307ce88a 100644
--- a/src/PathOrder.h
+++ b/src/PathOrder.h
@@ -6,6 +6,7 @@
#include "settings/ZSeamConfig.h" //To get the seam settings.
#include "utils/polygonUtils.h"
+#include "PathOrderPath.h"
namespace cura
{
@@ -27,68 +28,6 @@ template<typename PathType>
class PathOrder
{
public:
- /*!
- * Represents a path which has been optimised, the output of the ordering.
- *
- * This small data structure contains the vertex data of a path, where to
- * start along the path and in which direction to print it, as well as
- * whether the path should be closed (in the case of a polygon) or open (in
- * case of a polyline).
- *
- * After the ordering has completed, the \ref paths vector will be filled
- * with optimized paths.
- */
- struct Path
- {
- /*!
- * Construct a new planned path.
- *
- * The \ref converted field is not initialized yet. This can only be
- * done after all of the input paths have been added, to prevent
- * invalidating the pointers.
- */
- Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false)
- : vertices(vertices)
- , start_vertex(start_vertex)
- , is_closed(is_closed)
- , backwards(backwards)
- {}
-
- /*!
- * The vertex data of the path.
- */
- PathType vertices;
-
- /*!
- * Vertex data, converted into a Polygon so that the orderer knows how
- * to deal with this data.
- */
- ConstPolygonPointer converted;
-
- /*!
- * Which vertex along the path to start printing with.
- *
- * If this path represents a polyline, this will always be one of the
- * endpoints of the path; either 0 or ``vertices->size() - 1``.
- */
- size_t start_vertex;
-
- /*!
- * Whether the path should be closed at the ends or not.
- *
- * If this path should be closed, it represents a polygon. If it should
- * not be closed, it represents a polyline.
- */
- bool is_closed;
-
- /*!
- * Whether the path should be traversed in backwards direction.
- *
- * For a polyline it may be more efficient to print the path in
- * backwards direction, if the last vertex is closer than the first.
- */
- bool backwards;
- };
/*!
* After reordering, this contains the paths that need to be printed in the
@@ -98,7 +37,7 @@ public:
* pointer to the vertex data, whether to close the loop or not, the
* direction in which to print the path and where to start the path.
*/
- std::vector<Path> paths;
+ std::vector<PathOrderPath<PathType>> paths;
/*!
* The location where the nozzle is assumed to start from before printing
@@ -159,19 +98,6 @@ protected:
*/
constexpr static coord_t coincident_point_distance = 10;
- /*!
- * Get vertex data from the custom path type.
- *
- * This is a function that allows the reordering algorithm to work with any
- * type of input data structure. It provides a translation from the input
- * data structure that the user would like to have reordered to a data
- * structure that the reordering algorithm can work with. It's unknown how
- * the ``PathType`` object is structured or how to get the vertex data from
- * it. This function tells the optimizer how, but it needs to be specialized
- * for each different type that this class is used with. See the .cpp file
- * for examples and where to add a new specialization.
- */
- ConstPolygonRef getVertexData(const PathType path);
/*!
* In the current set of paths, detect all loops and mark them as such.
@@ -182,7 +108,7 @@ protected:
*/
void detectLoops()
{
- for(Path& path : paths)
+ for(PathOrderPath<PathType>& path : paths)
{
if(path.is_closed) //Already a polygon. No need to detect loops.
{
diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h
index 5c296aae9..6543db5a5 100644
--- a/src/PathOrderMonotonic.h
+++ b/src/PathOrderMonotonic.h
@@ -9,6 +9,7 @@
#include <unordered_map> //To track monotonic sequences.
#include "PathOrder.h"
+#include "PathOrderPath.h"
namespace cura
{
@@ -40,7 +41,7 @@ template<typename PathType>
class PathOrderMonotonic : public PathOrder<PathType>
{
public:
- using typename PathOrder<PathType>::Path;
+ using Path = PathOrderPath<PathType>;
using PathOrder<PathType>::coincident_point_distance;
PathOrderMonotonic(const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const Point start_point)
@@ -61,7 +62,7 @@ public:
//Get the vertex data and store it in the paths.
for(Path& path : this->paths)
{
- path.converted = this->getVertexData(path.vertices);
+ path.converted = path.getVertexData();
}
std::vector<Path> reordered; //To store the result in. At the end, we'll std::swap with the real paths.
@@ -72,7 +73,7 @@ public:
this->detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order.
for(Path& path : this->paths)
{
- if(path.is_closed || path.vertices.size() <= 1)
+ if(path.is_closed || path.vertices->size() <= 1)
{
reordered.push_back(path);
}
diff --git a/src/PathOrderOptimizer.cpp b/src/PathOrderOptimizer.cpp
deleted file mode 100644
index c5fe5a6ea..000000000
--- a/src/PathOrderOptimizer.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-//Copyright (c) 2020 Ultimaker B.V.
-//CuraEngine is released under the terms of the AGPLv3 or higher.
-
-#include "PathOrderOptimizer.h" //The definitions we're implementing here.
-#include "sliceDataStorage.h" //For SliceLayerPart.
-#include "WallToolPaths.h"
-
-//Since the PathOrderOptimizer is a template class, we will only implement the template specializations in this file.
-
-namespace cura
-{
-
- template<>
- ConstPolygonRef PathOrderOptimizer<ConstPolygonPointer>::getVertexData(ConstPolygonPointer path)
- {
- return *path;
- }
-
- template<>
- ConstPolygonRef PathOrderOptimizer<PolygonPointer>::getVertexData(PolygonPointer path)
- {
- return *path;
- }
-
- template<>
- ConstPolygonRef PathOrderOptimizer<const SkinPart*>::getVertexData(const SkinPart* path)
- {
- return path->outline.outerPolygon();
- }
-
- template<>
- ConstPolygonRef PathOrderOptimizer<const SliceLayerPart*>::getVertexData(const SliceLayerPart* path)
- {
- return path->outline.outerPolygon();
- }
-
- template<>
- ConstPolygonRef PathOrderOptimizer<const SupportInfillPart*>::getVertexData(const SupportInfillPart* path)
- {
- return path->outline.outerPolygon();
- }
- template<>
- ConstPolygonRef PathOrderOptimizer<const ExtrusionLine*>::getVertexData(const ExtrusionLine* path)
- {
- cached_vertices.emplace_back();
- Polygon& poly = cached_vertices.back();
- poly = path->toPolygon();
- return ConstPolygonRef(poly);
- }
-
-}
diff --git a/src/PathOrderOptimizer.h b/src/PathOrderOptimizer.h
index aa6aec883..e403fe7ac 100644
--- a/src/PathOrderOptimizer.h
+++ b/src/PathOrderOptimizer.h
@@ -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 PATHORDEROPTIMIZER_H
@@ -8,6 +8,7 @@
#include <unordered_set>
#include "InsetOrderOptimizer.h" // for makeOrderIncludeTransitive
+#include "PathOrderPath.h"
#include "pathPlanning/CombPath.h" //To calculate the combing distance if we want to use combing.
#include "pathPlanning/LinePolygonsCrossings.h" //To prevent calculating combing distances if we don't cross the combing borders.
#include "settings/EnumSettings.h" //To get the seam settings.
@@ -51,71 +52,6 @@ class PathOrderOptimizer
{
public:
/*!
- * Represents a path which has been optimized, the output of the
- * optimization.
- *
- * This small data structure contains the vertex data of a path, where to
- * start along the path and in which direction to print it, as well as
- * whether the path should be closed (in case of a polygon) or open (in case
- * of a polyline.
- *
- * After optimization is completed, the \ref paths vector will be filled
- * with optimized paths.
- */
- struct Path
- {
- /*!
- * Construct a new path.
- *
- * The \ref converted field is not initialized yet. This can only be
- * done after all of the input paths have been added, to prevent
- * invalidating the pointer.
- */
- Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false)
- : vertices(vertices)
- , start_vertex(start_vertex)
- , is_closed(is_closed)
- , backwards(backwards)
- {
- }
-
- /*!
- * The vertex data of the path.
- */
- PathType vertices;
-
- /*!
- * Vertex data, converted into a Polygon so that the optimizer knows
- * how to deal with this data.
- */
- ConstPolygonPointer converted;
-
- /*!
- * Which vertex along the path to start printing with.
- *
- * If this path represents a polyline, this will always be one of the
- * endpoints of the path; either 0 or ``vertices->size() - 1``.
- */
- size_t start_vertex;
-
- /*!
- * Whether the path should be closed at the ends or not.
- *
- * If this path should be closed, it represents a polygon. If it should
- * not be closed, it represents a polyline.
- */
- bool is_closed;
-
- /*!
- * Whether the path should be traversed in backwards direction.
- *
- * For a polyline it may be more efficient to print the path in
- * backwards direction, if the last vertex is closer than the first.
- */
- bool backwards;
- };
-
- /*!
* After optimizing, this contains the paths that need to be printed in the
* correct order.
*
@@ -123,7 +59,7 @@ public:
* pointer to the vertex data, whether or not to close the loop, the
* direction in which to print the path and where to start the path.
*/
- std::vector<Path> paths;
+ std::vector<PathOrderPath<PathType>> paths;
/*!
* The location where the nozzle is assumed to start from before printing
@@ -194,16 +130,15 @@ public:
}
//Get the vertex data and store it in the paths.
- cached_vertices.reserve(paths.size()); //Prevent pointer invalidation.
- for(Path& path : paths)
+ for(PathOrderPath<PathType>& path : paths)
{
- path.converted = getVertexData(path.vertices);
+ path.converted = path.getVertexData();
}
//If necessary, check polylines to see if they are actually polygons.
if(detect_loops)
{
- for(Path& path : paths)
+ for(PathOrderPath<PathType>& path : paths)
{
if(!path.is_closed)
{
@@ -218,7 +153,7 @@ public:
SparsePointGridInclusive<size_t> line_bucket_grid(snap_radius);
for(size_t i = 0; i < paths.size(); ++i)
{
- const Path& path = paths[i];
+ const PathOrderPath<PathType>& path = paths[i];
if (path.converted->empty())
{
continue;
@@ -242,7 +177,7 @@ public:
const bool precompute_start = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER;
if(precompute_start)
{
- for(Path& path : paths)
+ for(PathOrderPath<PathType>& path : paths)
{
if(!path.is_closed)
{
@@ -273,11 +208,11 @@ public:
assert(before_it != path_to_index.end());
is_blocking[before_it->second].emplace_back(after_it->second);
}
-
+
std::vector<bool> picked(paths.size(), false); //Fixed size boolean flag for whether each path is already in the optimized vector.
Point current_position = start_point;
- std::vector<Path> optimized_order; //To store our result in. At the end we'll std::swap.
+ std::vector<PathOrderPath<PathType>> optimized_order; //To store our result in. At the end we'll std::swap.
optimized_order.reserve(paths.size());
while(optimized_order.size() < paths.size())
{
@@ -310,7 +245,7 @@ public:
for(const size_t candidate_path_index : available_candidates)
{
- Path& path = paths[candidate_path_index];
+ PathOrderPath<PathType>& path = paths[candidate_path_index];
if(path.converted->empty()) //No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
{
if(best_distance2 == std::numeric_limits<coord_t>::max())
@@ -341,7 +276,7 @@ public:
}
}
- Path& best_path = paths[best_candidate];
+ PathOrderPath<PathType>& best_path = paths[best_candidate];
optimized_order.push_back(best_path);
picked[best_candidate] = true;
for (size_t unlocked_idx : is_blocking[best_candidate])
@@ -367,7 +302,7 @@ public:
if(reverse_direction)
{
//Reverse-insert the optimized order, to invert the ordering.
- std::vector<Path> reversed;
+ std::vector<PathOrderPath<PathType>> reversed;
//Don't replace with swap, assign or insert. They require functions that we can't implement for all template arguments for PathType.
reversed.reserve(optimized_order.size());
for(auto it = optimized_order.rbegin(); it != optimized_order.rend(); it++)
@@ -397,16 +332,6 @@ protected:
constexpr static coord_t coincident_point_distance = 10;
/*!
- * Some input data structures need to be converted to polygons before use.
- * For those, we need to store the vertex data somewhere during the lifetime
- * of the object. Store them here.
- *
- * For example, if the ``PathType`` is a list of ``ExtrusionJunction``s,
- * this will store the coordinates of those junctions.
- */
- std::vector<Polygon> cached_vertices;
-
- /*!
* Bucket grid to store the locations of the combing boundary.
*
* This is cached in order to speed up the collision checking with the
@@ -463,7 +388,7 @@ protected:
* endpoints rather than
* \return An index to a vertex in that path where printing must start.
*/
- size_t findStartLocation(const Path& path, const Point& target_pos)
+ size_t findStartLocation(const PathOrderPath<PathType>& path, const Point& target_pos)
{
if(!path.is_closed)
{
@@ -489,7 +414,7 @@ protected:
return vert;
}
- // Don't know the path-type here, or wether it has a simplify. Also, simplification occurs in-place, which is not wanted here: Copy the polygon.
+ // Don't know the path-type here, or whether it has a simplify. Also, simplification occurs in-place, which is not wanted here: Copy the polygon.
// A course simplification is needed, since Arachne has a tendency to 'smear' corners out over multiple line segments.
// Which in itself is a good thing, but will mess up the detection of sharp corners and such.
Polygon simple_poly(*path.converted);
@@ -506,10 +431,6 @@ protected:
// Paths, other than polygons, can be either clockwise or counterclockwise. Make sure this is detected.
const bool clockwise = simple_poly.orientation();
- //Find most extreme point in one direction. For the "actual loop" (see below), start from this point,
- //so it can act as a "tie breaker" if all differences in dist-score for a polygon fall within epsilon.
- //Direction/point should be the user-specified point if available, or an arbitrary point away from polygon otherwise.
- constexpr coord_t EPSILON = 25;
const Point focus_fixed_point = (seam_config.type == EZSeamType::USER_SPECIFIED)
? seam_config.pos
: Point(0, std::sqrt(std::numeric_limits<coord_t>::max())); //Use sqrt, so the squared size can be used when comparing distances.
@@ -532,7 +453,7 @@ protected:
const coord_t distance = (combing_boundary == nullptr)
? getDirectDistance(here, target_pos)
: getCombingDistance(here, target_pos);
- const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) ? 0 : distance / 1000000;
+ const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) ? 0 : static_cast<float>(distance) / 1000000;
const float corner_angle = (clockwise ? LinearAlg2D::getAngleLeft(previous, here, next) : LinearAlg2D::getAngleLeft(next, here, previous)) / M_PI - 1; //Between -1 and 1.
float score;
@@ -581,7 +502,21 @@ protected:
}
}
- if(score - EPSILON < best_score)
+ constexpr float EPSILON = 25.0;
+ if (fabs(best_score - score) <= EPSILON)
+ {
+ // add breaker for two candidate starting location with similar score
+ // if we don't do this then we (can) get an un-even seam
+ // ties are broken by favouring points with lower x-coord
+ // if x-coord for both points are equal then break ties by
+ // favouring points with lower y-coord
+ if (here.X != best_point.X ? here.X < best_point.X : here.Y < best_point.Y)
+ {
+ best_point = here;
+ }
+ best_score = std::min(best_score, score);
+ }
+ else if(score < best_score)
{
best_point = here;
best_score = score;
@@ -674,7 +609,7 @@ protected:
return rand() % polygon.size();
}
- bool isLoopingPolyline(const Path& path)
+ bool isLoopingPolyline(const PathOrderPath<PathType>& path)
{
if(path.converted->empty())
{
@@ -682,20 +617,6 @@ protected:
}
return vSize2(path.converted->back() - path.converted->front()) < coincident_point_distance * coincident_point_distance;
}
-
- /*!
- * Get vertex data from the custom path type.
- *
- * This is a function that allows the optimization algorithm to work with
- * any type of input data structure. It provides a translation from the
- * input data structure that the user would like to have ordered to a data
- * structure that the optimization algorithm can work with. It's unknown how
- * the ``PathType`` object is structured or how to get the vertex data from
- * it. This function tells the optimizer how, but it needs to be specialized
- * for each different type that this optimizer is used. See the .cpp file
- * for examples and where to add a new specialization.
- */
- ConstPolygonRef getVertexData(const PathType path);
};
template<typename PathType>
diff --git a/src/PathOrderPath.cpp b/src/PathOrderPath.cpp
new file mode 100644
index 000000000..855057ea8
--- /dev/null
+++ b/src/PathOrderPath.cpp
@@ -0,0 +1,50 @@
+//Copyright (c) 2022 Ultimaker B.V.
+//CuraEngine is released under the terms of the AGPLv3 or higher.
+
+#include "PathOrderPath.h" //The definitions we're implementing here.
+#include "sliceDataStorage.h" //For SliceLayerPart.
+#include "WallToolPaths.h"
+
+namespace cura
+{
+
+ template<>
+ ConstPolygonRef PathOrderPath<ConstPolygonPointer>::getVertexData()
+ {
+ return *vertices;
+ }
+
+ template<>
+ ConstPolygonRef PathOrderPath<PolygonPointer>::getVertexData()
+ {
+ return *vertices;
+ }
+
+ template<>
+ ConstPolygonRef PathOrderPath<const SkinPart*>::getVertexData()
+ {
+ return vertices->outline.outerPolygon();
+ }
+
+ template<>
+ ConstPolygonRef PathOrderPath<const SliceLayerPart*>::getVertexData()
+ {
+ return vertices->outline.outerPolygon();
+ }
+
+ template<>
+ ConstPolygonRef PathOrderPath<const SupportInfillPart*>::getVertexData()
+ {
+ return vertices->outline.outerPolygon();
+ }
+ template<>
+ ConstPolygonRef PathOrderPath<const ExtrusionLine*>::getVertexData()
+ {
+ if ( ! cached_vertices)
+ {
+ cached_vertices = vertices->toPolygon();
+ }
+ return ConstPolygonRef(*cached_vertices);
+ }
+
+}
diff --git a/src/PathOrderPath.h b/src/PathOrderPath.h
new file mode 100644
index 000000000..b113e81e2
--- /dev/null
+++ b/src/PathOrderPath.h
@@ -0,0 +1,107 @@
+//Copyright (c) 2022 Ultimaker B.V.
+//CuraEngine is released under the terms of the AGPLv3 or higher.
+
+#ifndef PATH_ORDER_PATH_H
+#define PATH_ORDER_PATH_H
+
+#include <optional>
+
+#include "settings/ZSeamConfig.h" //To get the seam settings.
+#include "utils/polygonUtils.h"
+
+namespace cura
+{
+
+/*!
+ * Represents a path which has been optimised, the output of the ordering.
+ *
+ * This small data structure contains the vertex data of a path, where to
+ * start along the path and in which direction to print it, as well as
+ * whether the path should be closed (in the case of a polygon) or open (in
+ * case of a polyline).
+ *
+ * After the ordering has completed, the \ref paths vector will be filled
+ * with optimized paths.
+ */
+template<typename PathType>
+struct PathOrderPath
+{
+ /*!
+ * Construct a new planned path.
+ *
+ * The \ref converted field is not initialized yet. This can only be
+ * done after all of the input paths have been added, to prevent
+ * invalidating the pointers.
+ */
+ PathOrderPath(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false)
+ : vertices(vertices)
+ , start_vertex(start_vertex)
+ , is_closed(is_closed)
+ , backwards(backwards)
+ {}
+
+ /*!
+ * The vertex data of the path.
+ */
+ PathType vertices;
+
+ /*!
+ * Vertex data, converted into a Polygon so that the orderer knows how
+ * to deal with this data.
+ */
+ ConstPolygonPointer converted;
+
+ /*!
+ * Which vertex along the path to start printing with.
+ *
+ * If this path represents a polyline, this will always be one of the
+ * endpoints of the path; either 0 or ``vertices->size() - 1``.
+ */
+ size_t start_vertex;
+
+ /*!
+ * Whether the path should be closed at the ends or not.
+ *
+ * If this path should be closed, it represents a polygon. If it should
+ * not be closed, it represents a polyline.
+ */
+ bool is_closed;
+
+ /*!
+ * Whether the path should be traversed in backwards direction.
+ *
+ * For a polyline it may be more efficient to print the path in
+ * backwards direction, if the last vertex is closer than the first.
+ */
+ bool backwards;
+
+
+ /*!
+ * Get vertex data from the custom path type.
+ *
+ * This is a function that allows the reordering algorithm to work with any
+ * type of input data structure. It provides a translation from the input
+ * data structure that the user would like to have reordered to a data
+ * structure that the reordering algorithm can work with. It's unknown how
+ * the ``PathType`` object is structured or how to get the vertex data from
+ * it. This function tells the optimizer how, but it needs to be specialized
+ * for each different type that this class is used with. See the .cpp file
+ * for examples and where to add a new specialization.
+ */
+ ConstPolygonRef getVertexData();
+
+protected:
+ /*!
+ * Some input data structures need to be converted to polygons before use.
+ * For those, we need to store the vertex data somewhere during the lifetime
+ * of the object. Store them here.
+ *
+ * For example, if the ``PathType`` is a list of ``ExtrusionJunction``s,
+ * this will store the coordinates of those junctions.
+ */
+ std::optional<Polygon> cached_vertices;
+};
+
+}
+
+#endif //PATH_ORDER_PATH_H
diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp
index 7ecd3a933..635838bcd 100644
--- a/src/SkeletalTrapezoidation.cpp
+++ b/src/SkeletalTrapezoidation.cpp
@@ -433,8 +433,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
}
separatePointyQuadEndNodes();
-
- graph.fixNodeDuplication();
graph.collapseSmallEdges();
@@ -484,7 +482,7 @@ void SkeletalTrapezoidation::separatePointyQuadEndNodes()
// vvvvvvvvvvvvvvvvvvvvv
//
-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;
@@ -1803,7 +1801,7 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c
{
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())
@@ -1971,7 +1969,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)
{
diff --git a/src/SkeletalTrapezoidation.h b/src/SkeletalTrapezoidation.h
index 49a404304..0dfb82d2a 100644
--- a/src/SkeletalTrapezoidation.h
+++ b/src/SkeletalTrapezoidation.h
@@ -120,7 +120,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:
/*!
@@ -161,8 +161,10 @@ protected:
/*!
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
+ *
+ * Binned by inset_idx.
*/
- 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)
diff --git a/src/SkeletalTrapezoidationGraph.cpp b/src/SkeletalTrapezoidationGraph.cpp
index b43b0e09c..44034cd44 100644
--- a/src/SkeletalTrapezoidationGraph.cpp
+++ b/src/SkeletalTrapezoidationGraph.cpp
@@ -175,39 +175,6 @@ bool STHalfEdgeNode::isLocalMaximum(bool strict) const
} while (edge = edge->twin->next, edge != incident_edge);
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)
{
diff --git a/src/SkeletalTrapezoidationGraph.h b/src/SkeletalTrapezoidationGraph.h
index 289c36645..11849262c 100644
--- a/src/SkeletalTrapezoidationGraph.h
+++ b/src/SkeletalTrapezoidationGraph.h
@@ -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.
diff --git a/src/SupportInfillPart.h b/src/SupportInfillPart.h
index 6c300357b..8fae3f230 100644
--- a/src/SupportInfillPart.h
+++ b/src/SupportInfillPart.h
@@ -31,7 +31,7 @@ public:
int inset_count_to_generate; //!< The number of insets need to be generated from the outline. This is not the actual insets that will be generated.
std::vector<std::vector<Polygons>> infill_area_per_combine_per_density; //!< a list of separated sub-areas which requires different infill densities and combined thicknesses
// for infill_areas[x][n], x means the density level and n means the thickness
- VariableWidthPaths wall_toolpaths; //!< Any walls go here, not in the areas, where they could be combined vertically (don't combine walls).
+ std::vector<VariableWidthLines> wall_toolpaths; //!< Any walls go here, not in the areas, where they could be combined vertically (don't combine walls). Binned by inset_idx.
SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate = 0);
diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp
index 7bf5be852..18b691866 100644
--- a/src/TopSurface.cpp
+++ b/src/TopSurface.cpp
@@ -62,6 +62,10 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage
const coord_t max_resolution = mesh.settings.get<coord_t>("meshfix_maximum_resolution");
const coord_t max_deviation = mesh.settings.get<coord_t>("meshfix_maximum_deviation");
const Ratio ironing_flow = mesh.settings.get<Ratio>("ironing_flow");
+ const bool enforce_monotonic_order = mesh.settings.get<bool>("ironing_monotonic");
+ constexpr size_t wall_line_count = 0;
+ const Point infill_origin = Point();
+ const bool skip_line_stitching = enforce_monotonic_order;
coord_t ironing_inset = -mesh.settings.get<coord_t>("ironing_inset");
if (pattern == EFillMethod::ZIG_ZAG)
@@ -81,8 +85,8 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage
}
Polygons ironed_areas = areas.offset(ironing_inset);
- Infill infill_generator(pattern, zig_zaggify_infill, connect_polygons, ironed_areas, line_width, line_spacing, infill_overlap, infill_multiplier, direction, layer.z - 10, shift, max_resolution, max_deviation);
- VariableWidthPaths ironing_paths;
+ Infill infill_generator(pattern, zig_zaggify_infill, connect_polygons, ironed_areas, line_width, line_spacing, infill_overlap, infill_multiplier, direction, layer.z - 10, shift, max_resolution, max_deviation, wall_line_count, infill_origin, skip_line_stitching);
+ std::vector<VariableWidthLines> ironing_paths;
Polygons ironing_polygons;
Polygons ironing_lines;
infill_generator.generate(ironing_paths, ironing_polygons, ironing_lines, mesh.settings);
@@ -124,7 +128,7 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage
}
}
- if(!mesh.settings.get<bool>("ironing_monotonic"))
+ if( ! enforce_monotonic_order)
{
layer.addLinesByOptimizer(ironing_lines, line_config, SpaceFillType::PolyLines);
}
diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp
index f46e4763e..a83b82de3 100644
--- a/src/WallToolPaths.cpp
+++ b/src/WallToolPaths.cpp
@@ -22,7 +22,6 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t nominal_bead
, bead_width_x(nominal_bead_width)
, inset_count(inset_count)
, wall_0_inset(wall_0_inset)
- , strategy_type(settings.get<StrategyType>("beading_strategy_type"))
, print_thin_walls(settings.get<bool>("fill_outline_gaps"))
, min_feature_size(settings.get<coord_t>("min_feature_size"))
, min_bead_width(settings.get<coord_t>("min_bead_width"))
@@ -39,7 +38,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(settings.get<StrategyType>("beading_strategy_type"))
, print_thin_walls(settings.get<bool>("fill_outline_gaps"))
, min_feature_size(settings.get<coord_t>("min_feature_size"))
, min_bead_width(settings.get<coord_t>("min_bead_width"))
@@ -49,7 +47,7 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0
{
}
-const VariableWidthPaths& WallToolPaths::generate()
+const std::vector<VariableWidthLines>& WallToolPaths::generate()
{
const coord_t smallest_segment = settings.get<coord_t>("meshfix_maximum_resolution");
const coord_t allowed_distance = settings.get<coord_t>("meshfix_maximum_deviation");
@@ -82,7 +80,6 @@ const VariableWidthPaths& WallToolPaths::generate()
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,
@@ -127,9 +124,9 @@ const VariableWidthPaths& WallToolPaths::generate()
}
-void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings)
+void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines>& toolpaths, const Settings& settings)
{
- const coord_t stitch_distance = settings.get<coord_t>("wall_line_width_x") / 3 + 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width.
+ const coord_t stitch_distance = settings.get<coord_t>("wall_line_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++)
{
@@ -138,18 +135,6 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting
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)
- {
- logError("Some even contour lines could not be closed into polygons!\n");
- assert(false && "Some even contour lines could not be closed into polygons!");
- // NOTE: if this assertion fails then revert the debugging code removed in this commit (git blame on this line)
- }
- }
-#endif // DEBUG
wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines
for (ExtrusionLine& wall_polygon : closed_polygons)
@@ -170,7 +155,7 @@ void WallToolPaths::stitchToolPaths(VariableWidthPaths& toolpaths, const Setting
}
}
-void WallToolPaths::removeSmallLines(VariableWidthPaths& toolpaths)
+void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines>& toolpaths)
{
for (VariableWidthLines& inset : toolpaths)
{
@@ -192,7 +177,7 @@ void WallToolPaths::removeSmallLines(VariableWidthPaths& toolpaths)
}
}
-void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths, const Settings& settings)
+void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines>& toolpaths, const Settings& settings)
{
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
{
@@ -206,7 +191,7 @@ void WallToolPaths::simplifyToolPaths(VariableWidthPaths& toolpaths, const Setti
}
}
-const VariableWidthPaths& WallToolPaths::getToolPaths()
+const std::vector<VariableWidthLines>& WallToolPaths::getToolPaths()
{
if (!toolpaths_generated)
{
@@ -215,7 +200,7 @@ const VariableWidthPaths& WallToolPaths::getToolPaths()
return toolpaths;
}
-void WallToolPaths::pushToolPaths(VariableWidthPaths& paths)
+void WallToolPaths::pushToolPaths(std::vector<VariableWidthLines>& paths)
{
if (! toolpaths_generated)
{
@@ -227,9 +212,9 @@ void WallToolPaths::pushToolPaths(VariableWidthPaths& paths)
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);
inner_contour.clear();
for (const VariableWidthLines& inset : toolpaths)
@@ -314,7 +299,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)
{
diff --git a/src/WallToolPaths.h b/src/WallToolPaths.h
index 6f491ba5d..96feca430 100644
--- a/src/WallToolPaths.h
+++ b/src/WallToolPaths.h
@@ -39,21 +39,21 @@ public:
/*!
* Generates the Toolpaths
- * \return A reference to the newly create ToolPaths
+ * \return A reference to the newly created ToolPaths. Binned by inset_idx.
*/
- 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
+ * \return a reference to the toolpaths. Binned by inset_idx.
*/
- const VariableWidthPaths& getToolPaths();
+ const std::vector<VariableWidthLines>& getToolPaths();
/*!
* 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.
+ * \param paths The already existing (or empty) paths these new toolpaths are pushed into. Binned by inset_idx.
*/
- void pushToolPaths(VariableWidthPaths& paths);
+ 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.
@@ -78,10 +78,10 @@ public:
/*!
* Removes empty paths from the toolpaths
- * \param toolpaths the VariableWidthPaths generated with \p generate()
+ * \param toolpaths the toolpaths binned by inset_idx 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);
protected:
/*!
@@ -91,20 +91,19 @@ protected:
*
* \param settings The settings as provided by the user
*/
- static void stitchToolPaths(VariableWidthPaths& toolpaths, const Settings& settings);
+ static void stitchToolPaths(std::vector<VariableWidthLines>& toolpaths, const Settings& settings);
/*!
* Remove polylines shorter than half the smallest line width along that polyline.
*/
- static void removeSmallLines(VariableWidthPaths& toolpaths);
+ 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, const Settings& settings);
private:
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
@@ -112,14 +111,13 @@ 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.
- StrategyType 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
coord_t transition_length; //<! The transitioning length when the amount of extrusion lines changes
bool toolpaths_generated; //<! Are the toolpaths generated
- VariableWidthPaths toolpaths; //<! The generated toolpaths
+ std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths binned by inset_idx.
Polygons inner_contour; //<! The inner contour of the generated toolpaths
const Settings& settings;
};
diff --git a/src/Wireframe2gcode.cpp b/src/Wireframe2gcode.cpp
index 1702a7cfc..e49439415 100644
--- a/src/Wireframe2gcode.cpp
+++ b/src/Wireframe2gcode.cpp
@@ -645,7 +645,7 @@ void Wireframe2gcode::processSkirt()
order.optimize();
const Settings& scene_settings = Application::getInstance().current_slice->scene.settings;
- for(const PathOrderOptimizer<PolygonPointer>::Path& path : order.paths)
+ for(const PathOrderPath<PolygonPointer>& path : order.paths)
{
gcode.writeTravel((*path.vertices)[path.start_vertex], scene_settings.get<Velocity>("speed_travel"));
for(size_t vertex_index = 0; vertex_index < path.vertices->size(); ++vertex_index)
diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp
index 45f472065..c43941674 100644
--- a/src/gcodeExport.cpp
+++ b/src/gcodeExport.cpp
@@ -1172,7 +1172,8 @@ void GCodeExport::writeFanCommand(double speed)
}
else if (speed > 0)
{
- *output_stream << "M106 S" << PrecisionedDouble{1, speed * 255 / 100};
+ const bool should_scale_zero_to_one = Application::getInstance().current_slice->scene.settings.get<bool>("machine_scale_fan_speed_zero_to_one");
+ *output_stream << "M106 S" << PrecisionedDouble{(should_scale_zero_to_one ? 2u : 1u), (should_scale_zero_to_one ? speed : speed * 255) / 100};
if (fan_number)
{
*output_stream << " P" << fan_number;
diff --git a/src/infill.cpp b/src/infill.cpp
index c5e20acbd..79c945e1a 100644
--- a/src/infill.cpp
+++ b/src/infill.cpp
@@ -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 <algorithm> //For std::sort.
@@ -46,7 +46,7 @@ static inline int computeScanSegmentIdx(int x, int line_width)
namespace cura
{
-Polygons Infill::generateWallToolPaths(VariableWidthPaths& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings)
+Polygons Infill::generateWallToolPaths(std::vector<VariableWidthLines>& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings)
{
outer_contour = outer_contour.offset(infill_overlap);
@@ -65,7 +65,7 @@ Polygons Infill::generateWallToolPaths(VariableWidthPaths& toolpaths, Polygons&
return inner_contour;
}
-void Infill::generate(VariableWidthPaths& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider, const LightningLayer* lightning_trees, const SliceMeshStorage* mesh)
+void Infill::generate(std::vector<VariableWidthLines>& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider, const LightningLayer* lightning_trees, const SliceMeshStorage* mesh)
{
if (outer_contour.empty())
{
@@ -79,7 +79,6 @@ void Infill::generate(VariableWidthPaths& toolpaths, Polygons& result_polygons,
//The lines along the edge must lie next to the border, not on it.
//This makes those algorithms a lot simpler.
if (pattern == EFillMethod::ZIG_ZAG //Zig-zag prints the zags along the walls.
- || pattern == EFillMethod::CONCENTRIC //Concentric at high densities needs to print alongside the walls, not overlapping them.
|| (zig_zaggify && (pattern == EFillMethod::LINES //Zig-zaggified infill patterns print their zags along the walls.
|| pattern == EFillMethod::TRIANGLES
|| pattern == EFillMethod::GRID
@@ -97,10 +96,10 @@ void Infill::generate(VariableWidthPaths& toolpaths, Polygons& result_polygons,
constexpr coord_t gap_wall_count = 1; // Only need one wall here, less even, in a sense.
constexpr coord_t wall_0_inset = 0; //Don't apply any outer wall inset for these. That's just for the outer wall.
WallToolPaths wall_toolpaths(inner_contour, infill_line_width, gap_wall_count, wall_0_inset, settings);
- VariableWidthPaths gap_fill_paths = wall_toolpaths.getToolPaths();
+ std::vector<VariableWidthLines> gap_fill_paths = wall_toolpaths.getToolPaths();
// Add the gap filling to the toolpaths and make the new inner contour 'aware' of the gap infill:
- // (Can't use getContours here, becasue only _some_ of the lines Arachne has generated are needed.)
+ // (Can't use getContours here, because only _some_ of the lines Arachne has generated are needed.)
Polygons gap_filled_areas;
for (const auto& var_width_line : gap_fill_paths)
{
@@ -169,13 +168,18 @@ void Infill::generate(VariableWidthPaths& toolpaths, Polygons& result_polygons,
});
result_polygons.erase(it, result_polygons.end());
- PolygonConnector connector(infill_line_width, infill_line_width * 3 / 2);
+ PolygonConnector connector(infill_line_width);
connector.add(result_polygons);
- result_polygons = connector.connect();
+ connector.add(toolpaths);
+ Polygons connected_polygons;
+ std::vector<VariableWidthLines> connected_paths;
+ connector.connect(connected_polygons, connected_paths);
+ result_polygons = connected_polygons;
+ toolpaths = connected_paths;
}
}
-void Infill::_generate(VariableWidthPaths& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider, const LightningLayer * lightning_trees, const SliceMeshStorage* mesh)
+void Infill::_generate(std::vector<VariableWidthLines>& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider, const LightningLayer * lightning_trees, const SliceMeshStorage* mesh)
{
if (inner_contour.empty()) return;
if (line_distance == 0) return;
@@ -248,8 +252,8 @@ void Infill::_generate(VariableWidthPaths& toolpaths, Polygons& result_polygons,
result_polygons.simplify(max_resolution, max_deviation);
- if (zig_zaggify ||
- pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::GYROID || pattern == EFillMethod::ZIG_ZAG)
+ if ( ! skip_line_stitching && (zig_zaggify ||
+ pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::GYROID || pattern == EFillMethod::ZIG_ZAG))
{ // don't stich for non-zig-zagged line infill types
Polygons stiched_lines;
PolylineStitcher<Polygons, Polygon, Point>::stitch(result_lines, stiched_lines, result_polygons, infill_line_width);
@@ -344,31 +348,30 @@ void Infill::generateLightningInfill(const LightningLayer* trees, Polygons& resu
result_lines.add(trees->convertToLines(inner_contour, infill_line_width));
}
-void Infill::generateConcentricInfill(VariableWidthPaths& toolpaths, const Settings& settings)
+void Infill::generateConcentricInfill(std::vector<VariableWidthLines>& toolpaths, const Settings& settings)
{
- constexpr coord_t wall_0_inset = 0; // Don't apply any outer wall inset for these. That's just for the outer wall.
- const bool iterative = line_distance > infill_line_width; // Do it all at once if there is not need for a gap, otherwise, iterate.
const coord_t min_area = infill_line_width * infill_line_width;
- Polygons current_inset = inner_contour.offset(infill_line_width / 2);
- do
+
+ Polygons current_inset = inner_contour;
+ while(true)
{
- if (iterative)
- {
- current_inset = current_inset.offset(-infill_line_width * 2).offset(infill_line_width * 2);
- }
- current_inset.simplify();
- if (current_inset.area() <= min_area)
+ //If line_distance is 0, start from the same contour as the previous line, except where the previous line closed up the shape.
+ //So we add the whole nominal line width first (to allow lines to be closer together than 1 line width if the line distance is smaller) and then subtract line_distance.
+ current_inset = current_inset.offset(infill_line_width - line_distance);
+ current_inset.simplify(); //Many insets lead to increasingly detailed shapes. Simplify to speed up processing.
+ if(current_inset.area() < min_area) //So small that it's inconsequential. Stop here.
{
break;
}
- const coord_t inset_wall_count = iterative ? 1 : std::numeric_limits<coord_t>::max();
+ constexpr size_t inset_wall_count = 1; //1 wall at a time.
+ constexpr coord_t wall_0_inset = 0; //Don't apply any outer wall inset for these. That's just for the outer wall.
WallToolPaths wall_toolpaths(current_inset, infill_line_width, inset_wall_count, wall_0_inset, settings);
- const VariableWidthPaths inset_paths = wall_toolpaths.getToolPaths();
-
+ const std::vector<VariableWidthLines> inset_paths = wall_toolpaths.getToolPaths();
toolpaths.insert(toolpaths.end(), inset_paths.begin(), inset_paths.end());
- current_inset = wall_toolpaths.getInnerContour().offset((infill_line_width / 2) - line_distance);
- } while (iterative);
+
+ current_inset = wall_toolpaths.getInnerContour();
+ }
}
void Infill::generateGridInfill(Polygons& result)
diff --git a/src/infill.h b/src/infill.h
index 161d27606..12ae8a76e 100644
--- a/src/infill.h
+++ b/src/infill.h
@@ -40,6 +40,7 @@ class Infill
coord_t max_deviation; //!< Max deviation fro the original poly when enforcing max_resolution
size_t wall_line_count; //!< Number of walls to generate at the boundary of the infill region, spaced \ref infill_line_width apart
const Point infill_origin; //!< origin of the infill pattern
+ bool skip_line_stitching; //!< Whether to bypass the line stitching normally performed for polyline type infills
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
bool skip_some_zags; //!< (ZigZag) Whether to skip some zags
@@ -64,6 +65,7 @@ public:
, coord_t max_deviation
, size_t wall_line_count = 0
, const Point& infill_origin = Point()
+ , bool skip_line_stitching = false
, bool connected_zigzags = false
, bool use_endpieces = false
, bool skip_some_zags = false
@@ -85,6 +87,7 @@ public:
, max_deviation(max_deviation)
, wall_line_count(wall_line_count)
, infill_origin(infill_origin)
+ , skip_line_stitching(skip_line_stitching)
, connected_zigzags(connected_zigzags)
, use_endpieces(use_endpieces)
, skip_some_zags(skip_some_zags)
@@ -101,7 +104,7 @@ public:
/*!
* Generate the infill.
*
- * \param toolpaths (output) The resulting variable-width paths (from the extra walls around the pattern).
+ * \param toolpaths (output) The resulting variable-width paths (from the extra walls around the pattern). Binned by inset_idx.
* \param result_polygons (output) The resulting polygons (from concentric infill)
* \param result_lines (output) The resulting line segments (from linear infill types)
* \param settings A settings storage to use for generating variable-width walls.
@@ -109,13 +112,13 @@ public:
* \param mesh A mesh for which to generate infill (should only be used for non-helper-mesh objects).
* \param[in] cross_fill_provider The cross fractal subdivision decision functor
*/
- void generate(VariableWidthPaths& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr);
+ void generate(std::vector<VariableWidthLines>& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_provider = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr);
/*!
* Generate the wall toolpaths of an infill area. It will return the inner contour and set the inner-contour.
* This function is called within the generate() function but can also be called stand-alone
*
- * \param toolpaths [out] The generated toolpaths
+ * \param toolpaths [out] The generated toolpaths. Binned by inset_idx.
* \param outer_contour [in,out] the outer contour, this is offsetted with the infill overlap
* \param wall_line_count [in] The number of walls that needs to be generated
* \param line_width [in] The optimum wall line width of the walls
@@ -123,12 +126,12 @@ public:
* \param settings [in] A settings storage to use for generating variable-width walls.
* \return The inner contour of the wall toolpaths
*/
- static Polygons generateWallToolPaths(VariableWidthPaths& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings);
+ static Polygons generateWallToolPaths(std::vector<VariableWidthLines>& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings);
private:
/*!
* Generate the infill pattern without the infill_multiplier functionality
*/
- void _generate(VariableWidthPaths& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_pattern = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr);
+ void _generate(std::vector<VariableWidthLines>& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_pattern = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr);
/*!
* Multiply the infill lines, so that any single line becomes [infill_multiplier] lines next to each other.
@@ -253,10 +256,10 @@ private:
/*!
* Generate sparse concentric infill
*
- * \param toolpaths (output) The resulting toolpaths
+ * \param toolpaths (output) The resulting toolpaths. Binned by inset_idx.
* \param inset_value The offset between each consecutive two polygons
*/
- void generateConcentricInfill(VariableWidthPaths& toolpaths, const Settings& settings);
+ void generateConcentricInfill(std::vector<VariableWidthLines>& toolpaths, const Settings& settings);
/*!
* Generate a rectangular grid of infill lines
diff --git a/src/pathPlanning/Comb.cpp b/src/pathPlanning/Comb.cpp
index 7585d4fef..b10df1aa1 100644
--- a/src/pathPlanning/Comb.cpp
+++ b/src/pathPlanning/Comb.cpp
@@ -33,7 +33,7 @@ Comb::Comb(const SliceDataStorage& storage, const LayerIndex layer_nr, const Pol
: storage(storage)
, layer_nr(layer_nr)
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
-, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
+, max_moveInside_distance2(offset_from_outlines * offset_from_outlines)
, offset_from_inside_to_outside(offset_from_outlines + travel_avoid_distance)
, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation
, boundary_inside_minimum( comb_boundary_inside_minimum ) // copy the boundary, because the partsView_inside will reorder the polygons
@@ -80,7 +80,7 @@ bool Comb::calc(const ExtruderTrain& train, Point start_point, Point end_point,
unsigned int end_inside_poly = NO_INDEX;
const bool end_inside = moveInside(boundary_inside_optimal, _end_inside, inside_loc_to_line_optimal.get(), end_point, end_inside_poly);
- unsigned int start_part_boundary_poly_idx = NO_INDEX; // Added initial value to stop MSVC throwing an exception in debug mode
+ unsigned int start_part_boundary_poly_idx = NO_INDEX; // Added initial value to stop MSVC throwing an exception in debug mode
unsigned int end_part_boundary_poly_idx = NO_INDEX;
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside_optimal.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside_optimal.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
@@ -213,11 +213,29 @@ bool Comb::calc(const ExtruderTrain& train, Point start_point, Point end_point,
}
else
{
- bool combing_succeeded = LinePolygonsCrossings::comb(*boundary_outside, getOutsideLocToLine(), start_crossing.out, end_crossing.out, comb_paths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
- if (!combing_succeeded)
+ CombPath tmp_comb_path;
+ bool combing_succeeded = LinePolygonsCrossings::comb(*boundary_outside, getOutsideLocToLine(), start_crossing.out, end_crossing.out, tmp_comb_path, offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, true);
+
+ if (combing_succeeded)
+ {
+ // add combing travel moves if the combing was successful
+ comb_paths.push_back(tmp_comb_path);
+ }
+ else if (fail_on_unavoidable_obstacles)
{
return false;
}
+ else
+ {
+ // if combing is not possible then move directly to the target destination
+ // this happens for instance when trying to avoid skin-regions and combing from
+ // an origin that is on a hole-boundary to a destination that is on the outline-border
+ comb_paths.emplace_back();
+ comb_paths.throughAir = true;
+ comb_paths.back().cross_boundary = true;
+ comb_paths.back().push_back(start_crossing.in_or_mid);
+ comb_paths.back().push_back(end_crossing.in_or_mid);
+ }
}
}
else
diff --git a/src/pathPlanning/GCodePath.cpp b/src/pathPlanning/GCodePath.cpp
index 15dbfa02f..bd0d57578 100644
--- a/src/pathPlanning/GCodePath.cpp
+++ b/src/pathPlanning/GCodePath.cpp
@@ -1,4 +1,4 @@
-//Copyright (c) 2018 Ultimaker B.V.
+//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "GCodePath.h"
@@ -6,11 +6,12 @@
namespace cura
{
-GCodePath::GCodePath(const GCodePathConfig& config, std::string mesh_id, const SpaceFillType space_fill_type, const Ratio flow, const bool spiralize, const Ratio speed_factor) :
+GCodePath::GCodePath(const GCodePathConfig& config, std::string mesh_id, const SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor) :
config(&config),
mesh_id(mesh_id),
space_fill_type(space_fill_type),
flow(flow),
+width_factor(width_factor),
speed_factor(speed_factor),
speed_back_pressure_factor(1.0),
retract(false),
@@ -33,12 +34,12 @@ bool GCodePath::isTravelPath() const
double GCodePath::getExtrusionMM3perMM() const
{
- return flow * config->getExtrusionMM3perMM();
+ return flow * width_factor * config->getExtrusionMM3perMM();
}
coord_t GCodePath::getLineWidthForLayerView() const
{
- return flow * config->getLineWidth() * config->getFlowRatio();
+ return flow * width_factor * config->getLineWidth() * config->getFlowRatio();
}
void GCodePath::setFanSpeed(double fan_speed)
diff --git a/src/pathPlanning/GCodePath.h b/src/pathPlanning/GCodePath.h
index da6af9320..ec29551e1 100644
--- a/src/pathPlanning/GCodePath.h
+++ b/src/pathPlanning/GCodePath.h
@@ -1,4 +1,4 @@
-//Copyright (c) 2018 Ultimaker B.V.
+//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef PATH_PLANNING_G_CODE_PATH_H
@@ -32,6 +32,7 @@ public:
std::string mesh_id; //!< Which mesh this path belongs to, if any. If it's not part of any mesh, the mesh ID should be 0.
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
Ratio flow; //!< A type-independent flow configuration
+ Ratio width_factor; //!< Adjustment to the line width. Similar to flow, but causes the speed_back_pressure_factor to be adjusted.
Ratio speed_factor; //!< A speed factor that is multiplied with the travel speed. This factor can be used to change the travel speed.
Ratio speed_back_pressure_factor; // <! The factor the (non-travel) speed should be multiplied with as a consequence of back pressure compensation.
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
@@ -56,11 +57,12 @@ public:
* \param space_fill_type The type of space filling of which this path is a
* part.
* \param flow The flow rate to print this path with.
+ * \param width_factor A multiplier on the line width.
* \param spiralize Gradually increment the z-coordinate while traversing
* \param speed_factor The factor that the travel speed will be multiplied with
* this path.
*/
- GCodePath(const GCodePathConfig& config, std::string mesh_id, const SpaceFillType space_fill_type, const Ratio flow, const bool spiralize, const Ratio speed_factor = 1.0);
+ GCodePath(const GCodePathConfig& config, std::string mesh_id, const SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor = 1.0);
/*!
* Whether this config is the config of a travel path.
diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp
index b411f53b1..24b3fc6e8 100644
--- a/src/settings/Settings.cpp
+++ b/src/settings/Settings.cpp
@@ -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 <cctype>
@@ -348,27 +348,6 @@ template<> EFillMethod Settings::get<EFillMethod>(const std::string& key) const
}
}
-template<> StrategyType Settings::get<StrategyType>(const std::string& key) const
-{
- const std::string& value = get<std::string>(key);
- if (value == "center_deviation")
- {
- return StrategyType::Center;
- }
- else if (value == "distributed")
- {
- return StrategyType::Distributed;
- }
- else if (value == "inward_distributed")
- {
- return StrategyType::InwardDistributed;
- }
- else //Default.
- {
- return StrategyType::None;
- }
-}
-
template<> EPlatformAdhesion Settings::get<EPlatformAdhesion>(const std::string& key) const
{
const std::string& value = get<std::string>(key);
@@ -569,11 +548,7 @@ template<> SlicingTolerance Settings::get<SlicingTolerance>(const std::string& k
template<> InsetDirection Settings::get<InsetDirection>(const std::string& key) const
{
const std::string& value = get<std::string>(key);
- if(value == "center_last")
- {
- return InsetDirection::CENTER_LAST;
- }
- else if(value == "outside_in")
+ if(value == "outside_in")
{
return InsetDirection::OUTSIDE_IN;
}
diff --git a/src/skin.cpp b/src/skin.cpp
index 7cba5874b..c229849a5 100644
--- a/src/skin.cpp
+++ b/src/skin.cpp
@@ -309,9 +309,9 @@ void SkinInfillAreaComputation::generateRoofing(SliceLayerPart& part)
for(SkinPart& skin_part : part.skin_parts)
{
Polygons no_air_above = generateNoAirAbove(part, roofing_layer_count);
- skin_part.roofing_fill = skin_part.skin_fill.difference(no_air_above);
- skin_part.skin_fill = skin_part.skin_fill.intersection(no_air_above);
+ skin_part.roofing_fill = skin_part.outline.difference(no_air_above);
+ skin_part.skin_fill = skin_part.outline.intersection(no_air_above);
// Insets are NOT generated for any layer if the top/bottom pattern is concentric.
// In this case, we still want to generate insets for the roofing layers based on the extra skin wall count,
// if the roofing pattern is not concentric.
@@ -350,10 +350,6 @@ void SkinInfillAreaComputation::generateRoofing(SliceLayerPart& part)
regenerateRoofingFillAndInnerInfill(part, skin_part);
}
}
- else
- {
- skin_part.skin_fill = skin_part.outline;
- }
}
}
diff --git a/src/sliceDataStorage.h b/src/sliceDataStorage.h
index dd45e5464..658932fb5 100644
--- a/src/sliceDataStorage.h
+++ b/src/sliceDataStorage.h
@@ -40,7 +40,7 @@ class SkinPart
{
public:
PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module. Includes both roofing and non-roofing.
- VariableWidthPaths inset_paths; //!< The insets represented as variable line-width paths. The insets are also known as perimeters or the walls.
+ std::vector<VariableWidthLines> inset_paths; //!< The insets represented as variable line-width paths. The insets are also known as perimeters or the walls. Binned by inset_idx.
Polygons skin_fill; //!< The part of the skin which is not roofing.
Polygons roofing_fill; //!< The inner infill which has air directly above
Polygons top_most_surface_fill; //!< The inner infill of the uppermost top layer which has air directly above.
@@ -66,8 +66,8 @@ public:
Polygons spiral_wall; //!< The centerline of the wall used by spiralize mode. Only computed if spiralize mode is enabled.
Polygons inner_area; //!< The area of the outline, minus the walls. This will be filled with either skin or infill.
std::vector<SkinPart> skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets.
- VariableWidthPaths wall_toolpaths; //!< toolpaths for walls, will replace(?) the insets
- VariableWidthPaths infill_wall_toolpaths; //!< toolpaths for the infill area's
+ std::vector<VariableWidthLines> wall_toolpaths; //!< toolpaths for walls, will replace(?) the insets. Binned by inset_idx.
+ std::vector<VariableWidthLines> infill_wall_toolpaths; //!< toolpaths for the walls of the infill areas. Binned by inset_idx.
/*!
* The areas inside of the mesh.
diff --git a/src/support.cpp b/src/support.cpp
index 8d1eba4e6..23e611ffb 100644
--- a/src/support.cpp
+++ b/src/support.cpp
@@ -75,15 +75,15 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage
const EFillMethod support_pattern = infill_extruder.settings.get<EFillMethod>("support_pattern");
const coord_t support_line_width = infill_extruder.settings.get<coord_t>("support_line_width");
- // the wall line count is used for calculating insets, and we generate support infill patterns within the insets
+ // The wall line count is used for calculating insets, and we generate support infill patterns within the insets
const size_t wall_line_count = infill_extruder.settings.get<size_t>("support_wall_count");
- // generate separate support islands
+ // Generate separate support islands
for (unsigned int layer_nr = 0; layer_nr < total_layer_count - 1; ++layer_nr)
{
unsigned int wall_line_count_this_layer = wall_line_count;
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG))
- { // the first layer will be printed wit ha grid pattern
+ { // The first layer will be printed with a grid pattern
wall_line_count_this_layer++;
}
assert(storage.support.supportLayers[layer_nr].support_infill_parts.empty() && "support infill part list is supposed to be uninitialized");
@@ -91,7 +91,7 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage
const Polygons& global_support_areas = global_support_areas_per_layer[layer_nr];
if (global_support_areas.size() == 0 || layer_nr < min_layer || layer_nr > max_layer)
{
- // initialize support_infill_parts empty
+ // Initialize support_infill_parts empty
storage.support.supportLayers[layer_nr].support_infill_parts.clear();
continue;
}
@@ -104,7 +104,7 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage
{
support_line_width_here *= infill_extruder.settings.get<Ratio>("initial_layer_line_width_factor");
}
- // we don't generate insets and infill area for the parts yet because later the skid/brim and prime
+ // We don't generate insets and infill area for the parts yet because later the skirt/brim and prime
// tower will remove themselves from the support, so the outlines of the parts can be changed.
SupportInfillPart support_infill_part(island_outline, support_line_width_here, wall_line_count_this_layer);
@@ -202,13 +202,13 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage)
{
SupportInfillPart& support_infill_part = support_infill_parts[part_idx];
- // NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method.
Polygons original_area = support_infill_part.getInfillArea();
- const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, wall_count, wall_width, overlap, infill_extruder.settings);
- if (infill_area.empty())
+ if (original_area.empty())
{
continue;
}
+ // NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method.
+ const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, wall_count, wall_width, overlap, infill_extruder.settings);
const AABB& this_part_boundary_box = support_infill_part.outline_boundary_box;
// calculate density areas for this island
@@ -495,7 +495,12 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp
switch (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type"))
{
case EPlatformAdhesion::BRIM:
- adhesion_size = skirt_brim_extruder.settings.get<coord_t>("skirt_brim_line_width") * skirt_brim_extruder.settings.get<Ratio>("initial_layer_line_width_factor") * skirt_brim_extruder.settings.get<size_t>("brim_line_count") + extra_skirt_line_width;
+ adhesion_size =
+ skirt_brim_extruder.settings.get<coord_t>("brim_width")
+ + skirt_brim_extruder.settings.get<coord_t>("skirt_brim_line_width")
+ * skirt_brim_extruder.settings.get<size_t>("brim_line_count")
+ * skirt_brim_extruder.settings.get<Ratio>("initial_layer_line_width_factor")
+ + extra_skirt_line_width;
break;
case EPlatformAdhesion::RAFT:
{
diff --git a/src/utils/ExtrusionLine.cpp b/src/utils/ExtrusionLine.cpp
index 7dd940c93..6523b4428 100644
--- a/src/utils/ExtrusionLine.cpp
+++ b/src/utils/ExtrusionLine.cpp
@@ -128,9 +128,7 @@ void ExtrusionLine::simplify(const coord_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;
}
@@ -210,8 +208,8 @@ coord_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
@@ -220,18 +218,20 @@ coord_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A,
* */
const coord_t ab_length = vSize(B - A);
const coord_t bc_length = vSize(C - B);
- const coord_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.
- weighted_average_width = (ab_length * B.w + bc_length * C.w) / vSize(C - A);
- return llabs(B.w - weighted_average_width) * ab_length + llabs(C.w - weighted_average_width) * bc_length;
+ const coord_t ab_weight = (A.w + B.w) / 2;
+ const coord_t bc_weight = (B.w + C.w) / 2;
+ weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / vSize(C - A);
+ 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;
return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
}
}
diff --git a/src/utils/ExtrusionLine.h b/src/utils/ExtrusionLine.h
index 32f72eba1..4a1ce2f87 100644
--- a/src/utils/ExtrusionLine.h
+++ b/src/utils/ExtrusionLine.h
@@ -269,6 +269,5 @@ public:
};
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
-using VariableWidthPaths = std::vector<VariableWidthLines>; //<! The toolpaths generated by libArachne, binned by inset aka Path
} // namespace cura
#endif // UTILS_EXTRUSION_LINE_H
diff --git a/src/utils/PolygonConnector.cpp b/src/utils/PolygonConnector.cpp
index 34ce980c8..534eb4654 100644
--- a/src/utils/PolygonConnector.cpp
+++ b/src/utils/PolygonConnector.cpp
@@ -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 "PolygonConnector.h"
@@ -9,260 +9,100 @@
namespace cura
{
-Polygons PolygonConnector::connect()
-{
- Polygons ret;
+PolygonConnector::PolygonConnector(const coord_t line_width)
+: line_width(line_width)
+{}
- if (input_polygons.empty())
- {
- return ret;
- }
-
- std::vector<Polygon> to_connect;
- to_connect.reserve(input_polygons.size());
- for (ConstPolygonPointer poly : input_polygons)
+void PolygonConnector::add(const Polygons& input)
+{
+ for (ConstPolygonRef poly : input)
{
- to_connect.emplace_back(*poly); // copy into list
+ input_polygons.push_back(poly);
}
+}
- while (!to_connect.empty())
+void PolygonConnector::add(const std::vector<VariableWidthLines>& input)
+{
+ for(const VariableWidthLines& lines : input)
{
- if (to_connect.size() == 1)
+ for(const ExtrusionLine& line : lines)
{
- ret.add(std::move(to_connect.back()));
- break;
+ input_paths.push_back(line);
}
- Polygon current = std::move(to_connect.back());
- to_connect.pop_back();
-
- std::optional<PolygonBridge> bridge = getBridge(current, to_connect);
- if (bridge)
- {
- all_bridges.push_back(*bridge); // just for keeping scores
- // remove other poly from the list and put the newly connected one on the list
- // i.e. replace the old other poly by the new one
- PolygonRef other_poly(*const_cast<ClipperLib::Path*>(bridge->a.to.poly.operator->())); // const casting a ConstPolygonPointer is difficult!
- other_poly = std::move(connectPolygonsAlongBridge(*bridge)); // connect the bridged parts and overwrite the other polygon with it.
+ }
+}
- // don't store the current poly, it has just been connected and stored
- }
- else
- {
- ret.add(std::move(current));
- }
+void PolygonConnector::connect(Polygons& output_polygons, std::vector<VariableWidthLines>& output_paths)
+{
+ std::vector<Polygon> result_polygons = connectGroup(input_polygons);
+ for(Polygon& polygon : result_polygons)
+ {
+ output_polygons.add(polygon);
}
- return ret;
+ std::vector<ExtrusionLine> result_paths = connectGroup(input_paths);
+ output_paths.push_back(result_paths);
}
-
-Polygon PolygonConnector::connectPolygonsAlongBridge(const PolygonConnector::PolygonBridge& bridge)
+Point PolygonConnector::getPosition(const Point& vertex) const
{
- // enforce the following orientations:
- //
- // <<<<<<X......X<<<<<<< poly2
- // ^ v
- // ^ v
- // ^ a b v bridge
- // ^ v
- // >>>>>>X......X>>>>>>> poly1
- //
- // this should work independent from whether it is a hole polygon or a outline polygon
- Polygon ret;
- addPolygonSegment(bridge.b.from, bridge.a.from, ret);
- addPolygonSegment(bridge.a.to, bridge.b.to, ret);
- return ret;
+ return vertex;
}
-void PolygonConnector::addPolygonSegment(const ClosestPolygonPoint& start, const ClosestPolygonPoint& end, PolygonRef result)
+Point PolygonConnector::getPosition(const ExtrusionJunction& junction) const
{
- // <<<<<<<.start end.<<<<<<<<
- // ^ v
- // ^ v
- // >>>>>>>.end.....start.>>>>>>>
- assert(start.poly == end.poly && "We can only bridge from one polygon from the other if both connections depart from the one polygon!");
- ConstPolygonRef poly = *end.poly;
- int16_t dir = getPolygonDirection(end, start); // we get the direction of the polygon in between the bridge connections, while we add the segment of the polygon not in between the segments
-
- result.add(start.p());
- bool first_iter = true;
- for (size_t vert_nr = 0; vert_nr < poly.size(); vert_nr++)
- {
- size_t vert_idx =
- (dir > 0)?
- (start.point_idx + 1 + vert_nr) % poly.size()
- : (static_cast<size_t>(start.point_idx) - vert_nr + poly.size()) % poly.size(); // cast in order to accomodate subtracting
- if (!first_iter // don't return without adding points when the starting point and ending point are on the same polygon segment
- && vert_idx == (end.point_idx + ((dir > 0)? 1 : 0)) % poly.size())
- { // we've added all verts of the original polygon segment between start and end
- break;
- }
- first_iter = false;
- result.add(poly[vert_idx]);
- }
- result.add(end.p());
+ return junction.p;
}
-int16_t PolygonConnector::getPolygonDirection(const ClosestPolygonPoint& from, const ClosestPolygonPoint& to)
+coord_t PolygonConnector::getWidth(const Point&) const
{
- assert(from.poly == to.poly && "We can only bridge from one polygon from the other if both connections depart from the one polygon!");
- ConstPolygonRef poly = *from.poly;
- if (from.point_idx == to.point_idx)
- {
- const Point prev_vert = poly[from.point_idx];
- const coord_t from_dist2 = vSize2(from.p() - prev_vert);
- const coord_t to_dist2 = vSize2(to.p() - prev_vert);
- return (to_dist2 > from_dist2)? 1 : -1;
- }
- // TODO: replace naive implementation by robust implementation
- // naive idea: there are less vertices in between the connection points than around
- const size_t a_to_b_vertex_count = (static_cast<size_t>(to.point_idx) - from.point_idx + poly.size()) % poly.size(); // cast in order to accomodate subtracting
- if (a_to_b_vertex_count > poly.size() / 2)
- {
- return -1; // from a to b is in the reverse direction as the vertices are saved in the polygon
- }
- else
- {
- return 1; // from a to b is in the same direction as the vertices are saved in the polygon
- }
+ return line_width;
}
-std::optional<PolygonConnector::PolygonBridge> PolygonConnector::getBridge(ConstPolygonRef from_poly, std::vector<Polygon>& to_polygons)
+coord_t PolygonConnector::getWidth(const ExtrusionJunction& junction) const
{
- // line distance between consecutive polygons should be at least the line_width
- const coord_t min_connection_length = line_width - 10;
- // Only connect polygons if they are next to each other
- // allow for a bit of wiggle room for when the polygons are very curved,
- // which causes any connection distance to be larger than the offset amount
- const coord_t max_connection_length = line_width * 3 / 2;
-
- std::optional<PolygonConnector::PolygonConnection> first_connection;
- std::optional<PolygonConnector::PolygonConnection> second_connection;
-
- Polygons to_polys;
- for (const Polygon& poly : to_polygons)
- {
- to_polys.add(poly);
- }
-
- std::function<bool (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>)> can_make_bridge =
- [&, this](std::pair<ClosestPolygonPoint, ClosestPolygonPoint> candidate)
- {
- first_connection.emplace(candidate.first, candidate.second);
- first_connection->to.poly = &to_polygons[first_connection->to.poly_idx]; // because it still refered to the local variable [to_polys]
- if (first_connection->getDistance2() > max_dist * max_dist)
- {
- return false;
- }
- second_connection = PolygonConnector::getSecondConnection(*first_connection);
- if (!second_connection)
- {
- return false;
- }
- return true;
- };
-
- std::pair<ClosestPolygonPoint, ClosestPolygonPoint> connection_points = PolygonUtils::findConnection(from_poly, to_polys, min_connection_length, max_connection_length, can_make_bridge);
-
- if (!connection_points.first.isValid() || !connection_points.second.isValid())
- { // We didn't find a connection which can make a bridge
- // We might have found a first_connection and a second_connection, but maybe they didn't satisfy all criteria.
- std::optional<PolygonConnector::PolygonBridge> uninitialized;
- return uninitialized;
- }
-
- if (first_connection && second_connection)
- {
- PolygonBridge result(*first_connection, *second_connection);
- // ensure that b is always the right connection and a the left
- Point a_vec = result.a.to.p() - result.a.from.p();
- Point shift = turn90CCW(a_vec);
- if (dot(shift, result.b.from.p() - result.a.from.p()) > 0)
- {
- std::swap(result.a, result.b);
- }
- return result;
- }
- else
- { // we couldn't find a connection; maybe the polygon was too small or maybe it is just too far away from other polygons.
- return std::optional<PolygonConnector::PolygonBridge>();
- }
+ return junction.w;
}
-std::optional<PolygonConnector::PolygonConnection> PolygonConnector::getSecondConnection(PolygonConnection& first)
+void PolygonConnector::addVertex(Polygon& polygonal, const Point& position, const coord_t) const
{
- bool forward = true;
- std::optional<ClosestPolygonPoint> from_a = PolygonUtils::getNextParallelIntersection(first.from, first.to.p(), line_width, forward);
- if (!from_a)
- { // then there's also not going to be a b
- return std::optional<PolygonConnector::PolygonConnection>();
- }
- std::optional<ClosestPolygonPoint> from_b = PolygonUtils::getNextParallelIntersection(first.from, first.to.p(), line_width, !forward);
+ polygonal.add(position);
+}
- std::optional<ClosestPolygonPoint> to_a = PolygonUtils::getNextParallelIntersection(first.to, first.from.p(), line_width, forward);
- if (!to_a)
- {
- return std::optional<PolygonConnector::PolygonConnection>();
- }
- std::optional<ClosestPolygonPoint> to_b = PolygonUtils::getNextParallelIntersection(first.to, first.from.p(), line_width, !forward);
+void PolygonConnector::addVertex(Polygon& polygonal, const Point& vertex) const
+{
+ polygonal.add(vertex);
+}
+void PolygonConnector::addVertex(ExtrusionLine& polygonal, const Point& position, const coord_t width) const
+{
+ polygonal.emplace_back(position, width, 1); //Perimeter indices don't make sense any more once perimeters are merged. Use 1 as placeholder, being the first "normal" wall.
+}
- const Point shift = turn90CCW(first.from.p() - first.to.p());
-
- std::optional<PolygonConnection> best;
- coord_t best_total_distance2 = std::numeric_limits<coord_t>::max();
- for (unsigned int from_idx = 0; from_idx < 2; from_idx++)
- {
- std::optional<ClosestPolygonPoint> from_opt = (from_idx == 0)? from_a : from_b;
- if (!from_opt)
- {
- continue;
- }
- for (unsigned int to_idx = 0; to_idx < 2; to_idx++)
- {
- std::optional<ClosestPolygonPoint> to_opt = (to_idx == 0)? to_a : to_b;
- // for each combination of from and to
- if (!to_opt)
- {
- continue;
- }
- ClosestPolygonPoint from = *from_opt;
- ClosestPolygonPoint to = *to_opt;
+void PolygonConnector::addVertex(ExtrusionLine& polygonal, const ExtrusionJunction& vertex) const
+{
+ polygonal.emplace_back(vertex);
+}
- coord_t from_projection = dot(from.p() - first.to.p(), shift);
- coord_t to_projection = dot(to.p() - first.to.p(), shift);
- if (from_projection * to_projection <= 0)
- { // ends lie on different sides of the first connection
- continue;
- }
+bool PolygonConnector::isClosed(Polygon&) const
+{
+ return true;
+}
- const coord_t total_distance2 = vSize2(from.p() - to.p()) + vSize2(from.p() - first.from.p()) + vSize(to.p() - first.to.p());
- if (total_distance2 < best_total_distance2)
- {
- best.emplace(from, to);
- best_total_distance2 = total_distance2;
- }
-
- if (to_opt == to_b)
- {
- break;
- }
- }
- if (from_opt == from_b)
- {
- break;
- }
- }
- if (!best || best_total_distance2 > max_dist * max_dist + 2 * (line_width + 10) * (line_width + 10))
- {
- return std::optional<PolygonConnector::PolygonConnection>();
- }
- else
- {
- return *best;
- }
+bool PolygonConnector::isClosed(ExtrusionLine& polygonal) const
+{
+ return vSize2(polygonal.front() - polygonal.back()) < 25;
}
+template<>
+ExtrusionLine PolygonConnector::createEmpty<ExtrusionLine>() const
+{
+ constexpr size_t inset_index = 1; //Specialising to set inset_index to 1 instead of maximum int. Connected polys are not specific to any inset.
+ constexpr bool is_odd = false;
+ ExtrusionLine result(inset_index, is_odd);
+ result.is_closed = true;
+ return result; //No copy, via RVO.
+}
}//namespace cura
diff --git a/src/utils/PolygonConnector.h b/src/utils/PolygonConnector.h
index 66d1dcd26..d29961ca0 100644
--- a/src/utils/PolygonConnector.h
+++ b/src/utils/PolygonConnector.h
@@ -1,4 +1,4 @@
-//Copyright (c) 2019 Ultimaker B.V.
+//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef UTILS_POLYGON_CONNECTOR_H
@@ -12,6 +12,7 @@
#include "IntPoint.h"
#include "polygon.h"
#include "polygonUtils.h"
+#include "linearAlg2D.h"
namespace cura
{
@@ -26,75 +27,166 @@ namespace cura
* o-----+ . +-----o .
* / \ .
* / \ .
- *
+ *
* This way two polygons become one.
- *
- * By repeating such a procedure many polygons can be connected into a single continuous line.
+ *
+ * By repeating such a procedure many polygons can be connected into a single
+ * continuous line.
+ *
+ * This connector can handle ordinary Polygons (which is assumed to print with a
+ * fixed, given line width) as well as variable-width paths. However with the
+ * paths it will only connect paths that form closed loops. Paths that don't
+ * form closed loops will be left unconnected.
+ *
+ * While this connector can connect Polygons and VariableWidthLines at the same
+ * time, it will never connect them together. This is done to keep the result
+ * and the algorithm simpler. Otherwise it would have to convert polygons to
+ * paths to make them partially variable width. This is not a use case we need
+ * right now, since infill patterns cannot generate a mix of these types.
+ *
+ * Basic usage of this class is as follows:
+ * ``
+ * PolygonConnector connector(line_width, max_dist); //Construct first.
+ * connector.add(polygons); //Add the polygons and paths you want to connect up.
+ * connector.add(paths);
+ * Polygons output_polygons; //Prepare some output variables to store results in.
+ * VariableWidthLines output_paths;
+ * connector.connect(output_polygons, output_paths);
+ * ``
*/
class PolygonConnector
{
#ifdef BUILD_TESTS
- FRIEND_TEST(PolygonConnectorTest, getBridgeTest);
- FRIEND_TEST(PolygonConnectorTest, connectionLengthTest);
+ FRIEND_TEST(PolygonConnectorTest, getBridgeNestedSquares);
+ FRIEND_TEST(PolygonConnectorTest, getBridgeAdjacentSquares);
+ FRIEND_TEST(PolygonConnectorTest, getBridgeClosest);
+ FRIEND_TEST(PolygonConnectorTest, getBridgeTooFar);
+ FRIEND_TEST(PolygonConnectorTest, getBridgeTooNarrow);
#endif
public:
- PolygonConnector(coord_t line_width, coord_t max_dist)
- : line_width(line_width - 5) // a bit less so that consecutive lines which have become connected can still connect to other lines
- // | | |
- // ----------o | ----------o | ----------o,,,,o
- // | | ==> | | ==>
- // -----o | | -----o----o | -----o----o----o
- // | | | | |
- // | | | o''''o | o''''o |
- // | | | | | | | | |
- , max_dist(max_dist)
- {}
+ /*!
+ * Create a connector object that can connect polygons.
+ *
+ * This specifies a few settings for the connector.
+ * \param line_width The width at which the polygons will be printed.
+ */
+ PolygonConnector(const coord_t line_width);
/*!
* Add polygons to be connected by a future call to \ref PolygonConnector::connect()
*/
- void add(const Polygons& input)
- {
- for (ConstPolygonRef poly : input)
- {
- input_polygons.push_back(poly);
- }
- }
+ void add(const Polygons& input);
+
+ /*!
+ * Add variable-width paths to be connected by a future call to
+ * \ref PolygonConnector::connect().
+ *
+ * Only the paths that form closed loops will be connected to each other.
+ * \param input The paths to connect.
+ */
+ void add(const std::vector<VariableWidthLines>& input);
/*!
* Connect as many polygons together as possible and return the resulting polygons.
- *
+ *
* Algorithm outline:
* try to connect a polygon to any of the other polygons
* - if succeeded, add to pool of polygons to connect
* - if failed, remove from pool and add to the result
+ * \param output_polygons Polygons that were connected as much as possible.
+ * These are expected to be empty to start with.
+ * \param output_paths Paths that were connected as much as possible. These
+ * are expected to be empty to start with.
*/
- Polygons connect();
+ void connect(Polygons& output_polygons, std::vector<VariableWidthLines>& output_paths);
protected:
coord_t line_width; //!< The distance between the line segments which connect two polygons.
- coord_t max_dist; //!< The maximal distance crossed by the connecting segments. Should be more than the \ref line_width in order to accomodate curved polygons.
- std::vector<ConstPolygonPointer> input_polygons; //!< The polygons assembled by calls to \ref PolygonConnector::add()
+ std::vector<Polygon> input_polygons; //!< The polygons assembled by calls to \ref PolygonConnector::add.
+ std::vector<ExtrusionLine> input_paths; //!< The paths assembled by calls to \ref PolygonConnector::add.
+
+ constexpr static Ratio max_gap = 0.5; //!< The maximum allowed gap between lines that get connected, in multiples of the local line width. Allows connections inside corners where the endpoints are slightly apart.
/*!
- * Line segment to connect two polygons
+ * Line segment to connect two polygons, with all the necessary information
+ * to connect them.
+ *
* A bridge consists of two such connections.
+ * \tparam Polygonal The type of polygon data to refer to, either Polygon or
+ * ExtrusionLine.
*/
+ template<typename Polygonal>
struct PolygonConnection
{
- ClosestPolygonPoint from; //!< from location in the source polygon
- ClosestPolygonPoint to; //!< to location in the destination polygon
+ /*!
+ * The polygon at the source of the connection.
+ */
+ Polygonal* from_poly;
- PolygonConnection(ClosestPolygonPoint from, ClosestPolygonPoint to)
- : from(from)
- , to(to)
- {}
+ /*!
+ * The index of the line segment at the source of the connection.
+ *
+ * This line segment is the one after the vertex with the same index.
+ */
+ size_t from_segment;
+
+ /*!
+ * The precise location of the source of the connection.
+ */
+ Point from_point;
+
+ /*!
+ * The polygon at the destination of the connection.
+ */
+ Polygonal* to_poly;
- coord_t getDistance2()
+ /*!
+ * The index of the line segment at the destination of the connection.
+ *
+ * This line segment is the one after the vertex with the same index.
+ */
+ size_t to_segment;
+
+ /*!
+ * The precise location of the destination of the connection.
+ */
+ Point to_point;
+
+ /*!
+ * Create a new connection.
+ * \param from_poly The polygon at the source of the connection.
+ * \param from_segment The index of the line segment at the source of
+ * the connection.
+ * \param from_point The precise location at the source of the
+ * connection.
+ * \param to_poly The polygon at the destination of the connection.
+ * \param to_segment The index of the line segment at the destination of
+ * the connection.
+ * \param to_point The precise location at the destination of the
+ * connection.
+ */
+ PolygonConnection(Polygonal* from_poly, const size_t from_segment, const Point from_point, Polygonal* to_poly, const size_t to_segment, const Point to_point)
+ : from_poly(from_poly)
+ , from_segment(from_segment)
+ , from_point(from_point)
+ , to_poly(to_poly)
+ , to_segment(to_segment)
+ , to_point(to_point)
{
- return vSize2(to.p() - from.p());
+ }
+
+ /*!
+ * Get the squared length of the connection.
+ *
+ * The squared length is faster to compute than the real length. Compare
+ * it only with the squared maximum distance.
+ */
+ coord_t getDistance2() const
+ {
+ return vSize2(from_point - to_point);
}
};
+
/*!
* Bridge to connect two polygons twice in order to make it into one polygon.
* A bridge consists of two connections.
@@ -106,51 +198,301 @@ protected:
*
* The resulting polygon will travel along the edges in a direction different from each other.
*/
+ template<typename Polygonal>
struct PolygonBridge
{
- PolygonConnection a; //!< first connection
- PolygonConnection b; //!< second connection
- PolygonBridge(PolygonConnection a, PolygonConnection b)
+ PolygonConnection<Polygonal> a; //!< first connection
+ PolygonConnection<Polygonal> b; //!< second connection
+ PolygonBridge(const PolygonConnection<Polygonal>& a, const PolygonConnection<Polygonal>& b)
: a(a), b(b)
{}
};
- std::vector<PolygonBridge> all_bridges; //!< All bridges generated during any call to \ref PolygonConnector::connect(). This is just for keeping scores for debugging etc.
+ /*!
+ * Connect a group of polygonal objects - either polygons or paths.
+ *
+ * This function is generic and will work the same way with either data
+ * type. However it will call specialized functions, for instance to get the
+ * position of a vertex in the data. This reduces code duplication.
+ * \tparam The type of polygonal data to connect.
+ * \param to_connect The input polygonals that need to be connected.
+ * \return The connected polygonals.
+ */
+ template<typename Polygonal>
+ std::vector<Polygonal> connectGroup(std::vector<Polygonal>& to_connect)
+ {
+ std::vector<Polygonal> result;
+ while(!to_connect.empty())
+ {
+ if(to_connect.size() == 1) //Nothing to connect it to any more.
+ {
+ result.push_back(to_connect[0]);
+ break;
+ }
+ Polygonal current = std::move(to_connect.back());
+ to_connect.pop_back();
+
+ if(!isClosed(current)) //Only bridge closed contours.
+ {
+ result.push_back(current);
+ continue;
+ }
+ std::optional<PolygonBridge<Polygonal>> bridge = getBridge(current, to_connect);
+ if(bridge)
+ {
+ connectPolygonsAlongBridge(*bridge, *bridge->a.to_poly); //Connect the polygons, and store the result in the to_poly.
+ //Don't store the current polygon. It has just been merged into the other one.
+ }
+ else //Can't connect this to anything. Leave it as-is.
+ {
+ result.push_back(current);
+ }
+ }
+ return result;
+ }
/*!
- * Connect the two polygons between which the bridge is computed.
+ * Get the position of a vertex, if the vertex is a point.
+ *
+ * This overload is simply the identity function. It will return the given
+ * vertex. This is a helper function to get the position of generic
+ * vertices.
+ * \param vertex The vertex to get the position of.
+ * \return The position of that vertex.
*/
- Polygon connectPolygonsAlongBridge(const PolygonBridge& bridge);
+ Point getPosition(const Point& vertex) const;
/*!
- * Add the segment from a polygon which is not removed by the bridge.
- *
- * This function gets called twice in order to connect two polygons together.
- *
- * Algorithm outline:
- * Add the one vertex from the \p start,
- * then add all vertices from the polygon in between
- * and then add the polygon location from the \p end.
- *
- * \param[out] result Where to apend the new vertices to
+ * Get the position of a vertex, if the vertex is a junction.
+ *
+ * This is a helper function to get the position of generic vertices.
+ * \param vertex The vertex to get the position of.
+ * \return The position of that vertex.
*/
- void addPolygonSegment(const ClosestPolygonPoint& start, const ClosestPolygonPoint& end, PolygonRef result);
+ Point getPosition(const ExtrusionJunction& vertex) const;
/*!
- * Get the direction between the polygon locations \p from and \p to.
- * This is intended to be the direction of the polygon segment of the short way around the polygon, not the long way around.
- *
- * The direction is positive for going in the same direction as the vertices are stored.
- * E.g. if \p from is vertex 7 and \p to is vertex 8 then the direction is positive.
- * Otherwise it is negative.
- *
- * \note \p from and \p to can also be points on the same segment, so their vertex index isn't everything to the algorithm.
- *
- * \note This function relies on some assumptions about the geometry of polygons you can encounter.
- * It cannot be used as a general purpose function for any two ClosestPolygonPoint
- * For large distances between \p from and \p to the output direction might be 'incorrect'.
+ * Get the width at a certain vertex.
+ *
+ * This is the overload that works for fixed-width polygons. They get their
+ * width from the width that was given at the constructor.
+ * \param vertex The vertex to get the width of.
+ * \return The line width of the polygon.
*/
- int16_t getPolygonDirection(const ClosestPolygonPoint& from, const ClosestPolygonPoint& to);
+ coord_t getWidth(const Point& vertex) const;
+
+ /*!
+ * Get the width at a certain junction.
+ *
+ * This is the overload that works for variable-width polygons. The width is
+ * stored in the junction then.
+ * \param vertex The vertex to get the width of.
+ * \return The line width at that vertex.
+ */
+ coord_t getWidth(const ExtrusionJunction& vertex) const;
+
+ /*!
+ * Add a vertex at the end of the polygonal object.
+ *
+ * This is the overload for fixed-width polygons. The width will be ignored.
+ * \param polygonal The polygon to add a vertex to.
+ * \param position The position of the vertex to add.
+ * \param width The width of the vertex to add, ignored in this overload.
+ */
+ void addVertex(Polygon& polygonal, const Point& position, const coord_t width) const;
+
+ /*!
+ * Add a vertex at the end of the polygonal object.
+ *
+ * This is the overload for fixed-width polygons.
+ * \param polygonal The polygon to add a vertex to.
+ * \param vertex The vertex to add.
+ */
+ void addVertex(Polygon& polygonal, const Point& vertex) const;
+
+ /*!
+ * Add a vertex at the end of the polygonal object.
+ *
+ * This is the overload for variable-width paths.
+ * \param polygonal The variable-width path to add a vertex to.
+ * \param position The position of the vertex to add.
+ * \param width The width of the vertex to add.
+ */
+ void addVertex(ExtrusionLine& polygonal, const Point& position, const coord_t width) const;
+
+ /*!
+ * Add a vertex at the end of the polygonal object.
+ *
+ * This is the overload for variable-width paths.
+ * \param polygonal The variable-width path to add a vertex to.
+ * \param vertex The vertex to add.
+ */
+ void addVertex(ExtrusionLine& polygonal, const ExtrusionJunction& vertex) const;
+
+ /*!
+ * Tests whether this is a closed polygonal object, rather than a polyline.
+ *
+ * Polygons are always closed, so this overload will always return true.
+ * \param polygonal The polygonal object to check.
+ * \return ``true``, indicating that this polygon is closed.
+ */
+ bool isClosed(Polygon& polygonal) const;
+
+ /*!
+ * Tests whether this is a closed polygonal object, rather than a polyline.
+ *
+ * If the endpoints of the extrusion line meet, it is a closed shape. If
+ * not, it is open.
+ * \param polygonal The polygonal object to check.
+ * \return ``true`` if that shape is closed, or ``false`` otherwise.
+ */
+ bool isClosed(ExtrusionLine& polygonal) const;
+
+ /*!
+ * Construct an empty polygonal object, without any vertices.
+ *
+ * This is to be template-specialized for every type. The default
+ * constructor for some types creates some invalid data.
+ * \return An empty polygonal object.
+ */
+ template<typename Polygonal>
+ Polygonal createEmpty() const
+ {
+ return Polygonal();
+ }
+
+ /*!
+ * Get the amount of space in between the polygons at the given connection.
+ *
+ * The space is the length of the connection, minus the width of the
+ * lines at the two endpoints.
+ * \param connection The connection to calculate the space of.
+ */
+ template<typename Polygonal>
+ coord_t getSpace(const PolygonConnection<Polygonal>& connection) const
+ {
+ const coord_t from_width = interpolateWidth(connection.from_point, (*connection.from_poly)[connection.from_segment], (*connection.from_poly)[(connection.from_segment + 1) % connection.from_poly->size()]);
+ const coord_t to_width = interpolateWidth(connection.to_point, (*connection.to_poly)[connection.to_segment], (*connection.to_poly)[(connection.to_segment + 1) % connection.to_poly->size()]);
+ return vSize(connection.to_point - connection.from_point) - from_width / 2 - to_width / 2;
+ }
+
+ /*!
+ * Get the local width at a certain position along a line segment.
+ *
+ * If the line segment has a variable width, the local line width will be
+ * interpolated between the two endpoints.
+ * \param position The position at which to get the line width. This should
+ * be (approximately) in between the position of the two vertices.
+ * \param a One of the vertices between which to interpolate.
+ * \param b The other vertex between which to interpolate.
+ */
+ template<typename Vertex>
+ coord_t interpolateWidth(const Point position, Vertex a, Vertex b) const
+ {
+ const coord_t total_length = vSize(getPosition(a) - getPosition(b));
+ if(total_length == 0) //Prevent division by 0 when the vertices are on top of each other.
+ {
+ return getWidth(a); //Just return one of them. They are on top of each other anyway.
+ }
+ const coord_t position_along_length = vSize(position - getPosition(a));
+ return round_divide(getWidth(b) * position_along_length, total_length) + round_divide(getWidth(a) * (total_length - position_along_length), total_length);
+ }
+
+ /*!
+ * Find the smallest connection between a polygon and a set of other
+ * candidate polygons to connect to.
+ */
+ template<typename Polygonal>
+ std::optional<PolygonBridge<Polygonal>> findConnection(Polygonal& from_poly, std::vector<Polygonal>& to_polygons)
+ {
+ //Optimise for finding the best connection.
+ coord_t best_distance = line_width * max_gap; //Allow up to the max_gap.
+ std::optional<PolygonConnection<Polygonal>> best_connection;
+ std::optional<PolygonConnection<Polygonal>> best_second_connection;
+
+ //The smallest connection will be from one of the vertices. So go through all of the vertices to find the closest place where they approach.
+ for(size_t poly_index = 0; poly_index < to_polygons.size(); ++poly_index)
+ {
+ if(!isClosed(to_polygons[poly_index]))
+ {
+ continue;
+ }
+ for(size_t to_index = 0; to_index < to_polygons[poly_index].size(); ++to_index)
+ {
+ const Point to_pos1 = getPosition(to_polygons[poly_index][to_index]);
+ const coord_t to_width1 = getWidth(to_polygons[poly_index][to_index]);
+ const Point to_pos2 = getPosition(to_polygons[poly_index][(to_index + 1) % to_polygons[poly_index].size()]);
+ const coord_t to_width2 = getWidth(to_polygons[poly_index][(to_index + 1) % to_polygons[poly_index].size()]);
+ const coord_t smallest_to_width = std::min(to_width1, to_width2);
+
+ for(size_t from_index = 0; from_index < from_poly.size(); ++from_index)
+ {
+ const Point from_pos1 = getPosition(from_poly[from_index]);
+ const coord_t from_width1 = getWidth(from_poly[from_index]);
+ const Point from_pos2 = getPosition(from_poly[(from_index + 1) % from_poly.size()]);
+ const coord_t from_width2 = getWidth(from_poly[(from_index + 1) % from_poly.size()]);
+ const coord_t smallest_from_width = std::min(from_width1, from_width2);
+
+ //Try a naive distance first. Faster to compute, but it may estimate the distance too small.
+ coord_t naive_dist = LinearAlg2D::getDistFromLine(from_pos1, to_pos1, to_pos2);
+ if(naive_dist - from_width1 - smallest_to_width < line_width * max_gap)
+ {
+ const Point closest_point = LinearAlg2D::getClosestOnLineSegment(from_pos1, to_pos1, to_pos2);
+ if(closest_point == to_pos2) //The last endpoint of a vertex is considered to be part of the next segment. Let that one handle it.
+ {
+ continue;
+ }
+ const coord_t width_at_closest = interpolateWidth(closest_point, to_polygons[poly_index][to_index], to_polygons[poly_index][(to_index + 1) % to_polygons[poly_index].size()]);
+ const coord_t distance = vSize(closest_point - from_pos1) - from_width1 - width_at_closest; //Actual, accurate distance to the other polygon.
+ if(distance < best_distance)
+ {
+ PolygonConnection<Polygonal> first_connection = PolygonConnection<Polygonal>(&from_poly, from_index, from_pos1, &to_polygons[poly_index], to_index, closest_point);
+ std::optional<PolygonConnection<Polygonal>> second_connection = getSecondConnection(first_connection, (width_at_closest + from_width1) / 2);
+ if(second_connection) //Second connection is also valid.
+ {
+ best_distance = distance;
+ best_connection = first_connection;
+ best_second_connection = second_connection;
+ }
+ }
+ }
+
+ //Also try the other way around: From the line segment of the from_poly to a vertex in the to_polygons.
+ naive_dist = LinearAlg2D::getDistFromLine(to_pos1, from_pos1, from_pos2);
+ if(naive_dist - smallest_from_width - to_width1 < line_width * max_gap)
+ {
+ const Point closest_point = LinearAlg2D::getClosestOnLineSegment(to_pos1, from_pos1, from_pos2);
+ if(closest_point == from_pos2) //The last endpoint of a vertex is considered to be part of the next segment. Let that one handle it.
+ {
+ continue;
+ }
+ const coord_t width_at_closest = interpolateWidth(closest_point, from_poly[from_index], from_poly[(from_index + 1) % from_poly.size()]);
+ const coord_t distance = vSize(closest_point - to_pos1) - width_at_closest - to_width1; //Actual, accurate distance.
+ if(distance < best_distance)
+ {
+ PolygonConnection<Polygonal> first_connection = PolygonConnection<Polygonal>(&from_poly, from_index, closest_point, &to_polygons[poly_index], to_index, to_pos1);
+ std::optional<PolygonConnection<Polygonal>> second_connection = getSecondConnection(first_connection, (to_width1 + width_at_closest) / 2);
+ if(second_connection) //Second connection is also valid.
+ {
+ best_distance = distance;
+ best_connection = first_connection;
+ best_second_connection = second_connection;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(best_connection)
+ {
+ return PolygonBridge<Polygonal>(*best_connection, *best_second_connection);
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }
/*!
* Get the bridge to cross between two polygons.
@@ -166,7 +508,88 @@ protected:
* - the first connection at a whole line distance away
* So as to try and find a bridge which is centered around the initiall found first connection
*/
- std::optional<PolygonBridge> getBridge(ConstPolygonRef poly, std::vector<Polygon>& polygons);
+ template<typename Polygonal>
+ std::optional<PolygonBridge<Polygonal>> getBridge(Polygonal& from_poly, std::vector<Polygonal>& to_polygons)
+ {
+ std::optional<PolygonBridge<Polygonal>> connection = findConnection(from_poly, to_polygons);
+ if(!connection) //We didn't find a connection. No bridge.
+ {
+ return std::nullopt;
+ }
+
+ //Ensure that B is always the right connection and A the left.
+ if(LinearAlg2D::pointIsLeftOfLine(connection->b.from_point, connection->a.from_point, connection->a.to_point) > 0)
+ {
+ std::swap(connection->a, connection->b);
+ }
+ return connection;
+ }
+
+ /*!
+ * Walk along a polygon to find the first point that is exactly ``distance``
+ * away from a given line.
+ *
+ * The resulting point does not have to be exactly on a vertex. Most likely
+ * it will be on a line segment.
+ * \param poly The polygonal shape along which to walk.
+ * \param start_index The vertex at which to start looking. This vertex
+ * should be on the wrong side of the line.
+ * \param distance The distance from the line at which the resulting point
+ * should be.
+ * \param line_a The line passes through this point.
+ * \param line_b The line also passes through this point.
+ * \param direction Use +1 to iterate in the forward direction through the
+ * polygon, or -1 to iterate backwards.
+ * \return If there is a point that is the correct distance from the line,
+ * the first such point is returned, and the segment index that it's on. If
+ * the polygon is entirely close to the line, returns
+ * ``std::nullopt``.
+ */
+ template<typename Polygonal>
+ std::optional<std::pair<Point, size_t>> walkUntilDistanceFromLine(const Polygonal& poly, const size_t start_index, const coord_t distance, const Point& line_a, const Point& line_b, const short direction)
+ {
+ const size_t poly_size = poly.size();
+ const coord_t line_magnitude = vSize(line_b - line_a); //Pre-compute, used for line distance calculation.
+ if(line_magnitude == 0)
+ {
+ return std::nullopt; //Line doesn't have a direction, so we can't be on any one side of it.
+ }
+
+ for(size_t index = (start_index + direction + poly_size) % poly_size; index != start_index; index = (index + direction + poly_size) % poly_size)
+ {
+ const Point vertex_pos = getPosition(poly[index]);
+ const coord_t vertex_distance = cross(line_a - line_b, line_a - vertex_pos) / line_magnitude; //Signed distance!
+ if(std::abs(vertex_distance) >= distance) //Further away from the line than the threshold.
+ {
+ //Interpolate over that last line segment to find the point at exactly the right distance.
+ const size_t previous_index = (index - direction + poly_size) % poly_size;
+ const Point previous_pos = getPosition(poly[previous_index]);
+ const coord_t previous_distance = cross(line_a - line_b, line_a - previous_pos) / line_magnitude;
+ if(previous_distance == vertex_distance) //0-length line segment, or parallel to line.
+ {
+ continue;
+ }
+ const double interpolation_pos = double(distance - previous_distance) / (vertex_distance - previous_distance);
+ const double interpolation_neg = double(-distance - previous_distance) / (vertex_distance - previous_distance);
+ double interpolation;
+ if(interpolation_pos >= 0 && interpolation_pos < 1)
+ {
+ interpolation = interpolation_pos;
+ }
+ else if(interpolation_neg >= 0 && interpolation_neg < 1)
+ {
+ interpolation = interpolation_neg;
+ }
+ else
+ {
+ continue;
+ }
+ const Point interpolated_point = previous_pos + (vertex_pos - previous_pos) * interpolation;
+ return std::make_pair(interpolated_point, (direction == +1) ? previous_index : index); //Choose the "earlier" index of the two, regardless of direction.
+ }
+ }
+ return std::nullopt; //None of the vertices were far enough away from the line.
+ }
/*!
* Get a connection parallel to a given \p first connection at an orthogonal distance line_width from the \p first connection.
@@ -179,7 +602,139 @@ protected:
* - check whether they are both on the same side of the \p first connection
* - choose the connection which woukd form the smalles bridge
*/
- std::optional<PolygonConnection> getSecondConnection(PolygonConnection& first);
+ template<typename Polygonal>
+ std::optional<PolygonConnection<Polygonal>> getSecondConnection(PolygonConnection<Polygonal>& first, const coord_t adjacent_distance)
+ {
+ std::optional<PolygonConnection<Polygonal>> result = std::nullopt;
+ coord_t best_connection_length = std::numeric_limits<coord_t>::max();
+
+ //Find the four intersections, on both sides of the initial connection, and on both polygons.
+ std::optional<std::pair<Point, size_t>> from_forward_intersection = walkUntilDistanceFromLine(*first.from_poly, first.from_segment, adjacent_distance, first.from_point, first.to_point, +1);
+ std::optional<std::pair<Point, size_t>> from_backward_intersection = walkUntilDistanceFromLine(*first.from_poly, first.from_segment, adjacent_distance, first.from_point, first.to_point, -1);
+ std::optional<std::pair<Point, size_t>> to_forward_intersection = walkUntilDistanceFromLine(*first.to_poly, first.to_segment, adjacent_distance, first.from_point, first.to_point, +1);
+ std::optional<std::pair<Point, size_t>> to_backward_intersection = walkUntilDistanceFromLine(*first.to_poly, first.to_segment, adjacent_distance, first.from_point, first.to_point, -1);
+
+ for(const std::optional<std::pair<Point, size_t>>& from_intersection : {from_forward_intersection, from_backward_intersection})
+ {
+ if(!from_intersection)
+ {
+ continue;
+ }
+ //Find the shortest of the connections in the to_poly.
+ const bool original_side = LinearAlg2D::pointIsLeftOfLine(first.to_point, first.from_point, from_intersection->first) > 0;
+ for(const std::optional<std::pair<Point, size_t>>& to_intersection : {to_forward_intersection, to_backward_intersection})
+ {
+ if(!to_intersection)
+ {
+ continue;
+ }
+ const bool current_side = LinearAlg2D::pointIsLeftOfLine(to_intersection->first, first.from_point, from_intersection->first) > 0;
+ if (original_side != current_side)
+ {
+ continue;
+ }
+ PolygonConnection<Polygonal> connection(first.from_poly, from_intersection->second, from_intersection->first, first.to_poly, to_intersection->second, to_intersection->first);
+ const coord_t connection_length = getSpace(connection);
+ if(connection_length < max_gap * line_width && connection_length < best_connection_length) //Connection is allowed.
+ {
+ result = connection;
+ best_connection_length = connection_length;
+ }
+ }
+ }
+ return result;
+ }
+
+ template<typename Polygonal>
+ void connectPolygonsAlongBridge(const PolygonBridge<Polygonal>& bridge, Polygonal& result)
+ {
+ //We'll traverse the following path:
+ //
+ // <<<<<<X......X<<<<<<< to_poly
+ // ^ v
+ // ^ v
+ // ^ a b v bridge
+ // ^ v
+ // >>>>>>X......X>>>>>>> from_poly
+ //
+ //To do this, from_poly and to_poly might need to be traversed in reverse order. This function figures all of that out.
+
+ Polygonal ret = createEmpty<Polygonal>(); //Create a temporary result that we'll move into the result.
+
+ const size_t from_size = bridge.b.from_poly->size();
+ //Add the from-endpoint of B.
+ const coord_t b_from_width = interpolateWidth(bridge.b.from_point, (*bridge.b.from_poly)[bridge.b.from_segment], (*bridge.b.from_poly)[(bridge.b.from_segment + 1) % from_size]);
+ addVertex(ret, bridge.b.from_point, b_from_width);
+
+ //Add the from-polygonal from B to A.
+ short forwards;
+ if(bridge.a.from_segment == bridge.b.from_segment) //If we start and end on the same segment, iterate in the direction from A to B.
+ {
+ const Point vertex = getPosition((*bridge.b.from_poly)[bridge.b.from_segment]); //Same vertex for A and B.
+ const Point next_vertex = getPosition((*bridge.b.from_poly)[(bridge.b.from_segment + 1) % from_size]);
+ const Point direction = next_vertex - vertex; //Direction we'd go into when forward iterating.
+ const Point a_to_b = bridge.b.from_point - bridge.a.from_point;
+ forwards = vSize2(direction - a_to_b) < vSize2(-direction - a_to_b);
+ }
+ else
+ {
+ //If not the same segment, traverse in whichever direction is the long way around.
+ forwards = ((bridge.b.from_segment + from_size - bridge.a.from_segment) % from_size) < ((bridge.a.from_segment + from_size - bridge.b.from_segment) % from_size);
+ }
+ size_t first_segment = forwards ? (bridge.b.from_segment + 1) % from_size : (bridge.b.from_segment + from_size) % from_size;
+ size_t last_segment = forwards ? bridge.a.from_segment : bridge.a.from_segment;
+ if(first_segment == last_segment) last_segment = (last_segment + from_size - 2 * forwards + 1) % from_size;
+ size_t i = first_segment;
+ do //Since we might start and end on the same segment, do a do_while loop to iterate at least once.
+ {
+ addVertex(ret, (*bridge.b.from_poly)[i]);
+ i = (i + 2 * forwards - 1 + from_size) % from_size;
+ }
+ while(i != (last_segment + from_size + 2 * forwards - 1) % from_size);
+
+ //Add the from-endpoint of A.
+ const coord_t a_from_width = interpolateWidth(bridge.a.from_point, (*bridge.b.from_poly)[bridge.a.from_segment], (*bridge.b.from_poly)[(bridge.a.from_segment + 1) % from_size]);
+ addVertex(ret, bridge.a.from_point, a_from_width);
+
+ const size_t to_size = bridge.b.to_poly->size();
+ //Add the to-endpoint of A.
+ const coord_t a_to_width = interpolateWidth(bridge.a.to_point, (*bridge.a.to_poly)[bridge.a.to_segment], (*bridge.a.to_poly)[(bridge.a.to_segment + 1) % to_size]);
+ addVertex(ret, bridge.a.to_point, a_to_width);
+
+ //Add the to_polygonal from A to B.
+ if(bridge.a.to_segment == bridge.b.to_segment)
+ {
+ const Point vertex = getPosition((*bridge.b.to_poly)[bridge.b.to_segment]); //Same vertex for A and B.
+ const Point next_vertex = getPosition((*bridge.b.to_poly)[(bridge.b.to_segment + 1) % to_size]);
+ const Point direction = next_vertex - vertex;
+ const Point a_to_b = bridge.b.to_point - bridge.a.to_point;
+ forwards = vSize2(direction - a_to_b) > vSize2(-direction - a_to_b);
+ }
+ else
+ {
+ forwards = ((bridge.a.to_segment + to_size - bridge.b.to_segment) % to_size) < ((bridge.b.to_segment + to_size - bridge.a.to_segment) % to_size);
+ }
+ first_segment = forwards ? (bridge.a.to_segment + 1) % to_size : bridge.a.to_segment;
+ size_t end_segment = forwards ? (bridge.b.to_segment + 1) % to_size : bridge.b.to_segment;
+ i = first_segment;
+ do
+ {
+ addVertex(ret, (*bridge.b.to_poly)[i]);
+ i = (i + 2 * forwards - 1 + to_size) % to_size;
+ }
+ while(i != end_segment);
+
+ //Add the to-endpoint of B.
+ const coord_t b_to_width = interpolateWidth(bridge.b.to_point, (*bridge.b.to_poly)[bridge.b.to_segment], (*bridge.b.to_poly)[(bridge.b.to_segment + 1) % to_size]);
+ addVertex(ret, bridge.b.to_point, b_to_width);
+
+ if(getPosition(ret.back()) != getPosition(ret.front()))
+ {
+ addVertex(ret, getPosition(ret.front()), getWidth(ret.front()));
+ }
+
+ result = std::move(ret); //Override the result with the new combined shape.
+ }
};
diff --git a/src/utils/PolylineStitcher.cpp b/src/utils/PolylineStitcher.cpp
index be71cee92..8353d41b8 100644
--- a/src/utils/PolylineStitcher.cpp
+++ b/src/utils/PolylineStitcher.cpp
@@ -40,5 +40,17 @@ bool PolylineStitcher<Polygons, Polygon, Point>::canConnect(const Polygon&, cons
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 cura
diff --git a/src/utils/PolylineStitcher.h b/src/utils/PolylineStitcher.h
index 693c22a44..5f03a0bda 100644
--- a/src/utils/PolylineStitcher.h
+++ b/src/utils/PolylineStitcher.h
@@ -23,17 +23,34 @@ 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 then \p snap_distance
- * but always try to take the shortest connection possible.
+ * 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.
+ * 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.
+ * \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.
+ * \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 = MM2INT(0.1), coord_t snap_distance = 10)
{
@@ -62,6 +79,7 @@ public:
}
processed[line_idx] = true;
const auto line = lines[line_idx];
+ bool should_close = isOdd(line);
Path chain = line;
bool closest_is_closing_polygon = false;
@@ -71,7 +89,8 @@ public:
{ // try extending chain in the other direction
chain.reverse();
}
-
+ coord_t chain_length = chain.polylineLength();
+
while (true)
{
Point from = make_point(chain.back());
@@ -80,7 +99,7 @@ public:
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, go_in_reverse_direction, max_stitch_distance, snap_distance]
+ [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;
@@ -91,15 +110,23 @@ public:
}
if(vSize2(nearby.p() - make_point(chain.front())) < snap_distance * snap_distance)
{
- if (chain.polylineLength() + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline
+ 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;
- dist += 10; // 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
+ if(!should_close)
+ {
+ dist += 10; // 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 -= 10; //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
@@ -139,6 +166,7 @@ public:
coord_t segment_dist = vSize(make_point(chain.back()) - closest.p());
assert(segment_dist <= max_stitch_distance + 10);
+ const size_t old_size = chain.size();
if (closest.point_idx == 0)
{
auto start_pos = (*closest.polygons)[closest.poly_idx].begin();
@@ -157,6 +185,11 @@ public:
}
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 += vSize(chain[i] - chain[i - 1]);
+ }
+ 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;
}
@@ -199,6 +232,8 @@ public:
* (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 cura
diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp
index dd876bead..045f0cd36 100644
--- a/src/utils/SVG.cpp
+++ b/src/utils/SVG.cpp
@@ -24,6 +24,7 @@ std::string SVG::toString(Color color) const
case SVG::Color::RED: return "red";
case SVG::Color::BLUE: return "blue";
case SVG::Color::GREEN: return "green";
+ case SVG::Color::LIME: return "lime";
case SVG::Color::ORANGE: return "orange";
case SVG::Color::MAGENTA: return "magenta";
case SVG::Color::YELLOW: return "yellow";
@@ -332,7 +333,7 @@ void SVG::writePolyline(ConstPolygonRef poly, const ColorObject color, const flo
}
}
-void SVG::writePaths(const VariableWidthPaths& paths, const ColorObject color, const float width_factor) const
+void SVG::writePaths(const std::vector<VariableWidthLines>& paths, const ColorObject color, const float width_factor) const
{
for(const VariableWidthLines& lines : paths)
{
diff --git a/src/utils/SVG.h b/src/utils/SVG.h
index 1f6dbfb79..c4418ef86 100644
--- a/src/utils/SVG.h
+++ b/src/utils/SVG.h
@@ -26,6 +26,7 @@ public:
RED,
BLUE,
GREEN,
+ LIME,
ORANGE,
MAGENTA,
YELLOW,
@@ -152,7 +153,7 @@ public:
* \param color The color to draw the paths with.
* \param width_factor A multiplicative factor on the line widths.
*/
- void writePaths(const VariableWidthPaths& paths, const ColorObject color = Color::BLACK, const float width_factor = 1.0) const;
+ void writePaths(const std::vector<VariableWidthLines>& paths, const ColorObject color = Color::BLACK, const float width_factor = 1.0) const;
/*!
* Draw variable-width lines into the image.
diff --git a/src/utils/math.h b/src/utils/math.h
index 6909b01ba..867cc6cad 100644
--- a/src/utils/math.h
+++ b/src/utils/math.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.
#ifndef UTILS_MATH_H
diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp
index 52fdb14e2..4fbecd193 100644
--- a/src/utils/polygonUtils.cpp
+++ b/src/utils/polygonUtils.cpp
@@ -87,7 +87,7 @@ void PolygonUtils::spreadDots(PolygonsPointIndex start, PolygonsPointIndex end,
std::vector<Point> PolygonUtils::spreadDotsArea(const Polygons& polygons, coord_t grid_size)
{
- VariableWidthPaths dummy_toolpaths;
+ std::vector<VariableWidthLines> dummy_toolpaths;
Settings dummy_settings;
Infill infill_gen(EFillMethod::LINES, false, false, polygons, 0, grid_size, 0, 1, 0, 0, 0, 0, 0);
Polygons result_polygons;
@@ -687,94 +687,6 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
}
}
-
-
-std::pair<ClosestPolygonPoint, ClosestPolygonPoint> PolygonUtils::findConnection(ConstPolygonRef poly1, Polygons& polys2, coord_t min_connection_length, coord_t max_connection_length, std::function<bool (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>)> precondition)
-{
- ClosestPolygonPoint invalid;
- std::pair<ClosestPolygonPoint, ClosestPolygonPoint> ret = std::make_pair(invalid, invalid);
- if (poly1.empty() || polys2.empty())
- {
- return ret;
- }
-
- const coord_t min_connection_dist2 = min_connection_length * min_connection_length;
- const coord_t max_connection_dist2 = max_connection_length * max_connection_length;
-
- auto grid = PolygonUtils::createLocToLineGrid(polys2, max_connection_length);
-
-
- std::unordered_set<std::pair<size_t, PolygonsPointIndex>> checked_segment_pairs; // pairs of index into segment start on poly1 and PolygonsPointIndex to segment start on polys2
-
- for (size_t point_idx = 0; point_idx < poly1.size(); point_idx++)
- {
- std::function<bool (const PolygonsPointIndex&)> process_elem_func =
- [&, point_idx](const PolygonsPointIndex& line_from)
- {
- std::pair<size_t, PolygonsPointIndex> segment_pair = std::make_pair(point_idx, line_from);
- if (checked_segment_pairs.count(segment_pair) > 0)
- { // these two line segments were already checked
- return true; // continue looking for connections
- }
-
- Point a1 = poly1[point_idx];
- Point a2 = poly1[(point_idx + 1) % poly1.size()];
- Point b1 = line_from.p();
- Point b2 = line_from.next().p();
- std::pair<Point, Point> connection = LinearAlg2D::getClosestConnection(a1, a2, b1, b2);
- coord_t dist2 = vSize2(connection.first - connection.second);
- ret = std::make_pair(
- ClosestPolygonPoint(connection.first, point_idx, poly1),
- ClosestPolygonPoint(connection.second, line_from.point_idx, polys2[line_from.poly_idx], line_from.poly_idx));
- if (min_connection_dist2 < dist2 && dist2 < max_connection_dist2
- && precondition(ret))
- {
- return false; // stop the search; break the for-loop
- }
-
- checked_segment_pairs.emplace(point_idx, line_from);
- return true; // continue looking for connections
- };
-
- std::pair<Point, Point> line = std::make_pair(poly1[point_idx], poly1[(point_idx + 1) % poly1.size()]);
- Point normal_vector = normal(turn90CCW(line.second - line.first), max_connection_length);
- std::pair<Point, Point> line2 = std::make_pair(line.first + normal_vector, line.second + normal_vector); // for neighborhood around the line
- std::pair<Point, Point> line3 = std::make_pair(line.first - normal_vector, line.second - normal_vector); // for neighborhood around the line
-
- bool continue_;
- continue_ = grid->processLine(line, process_elem_func);
- if (!continue_) break;
- continue_ = grid->processLine(line2, process_elem_func);
- if (!continue_) break;
- continue_ = grid->processLine(line3, process_elem_func);
- if (!continue_) break;
- }
- ret.first.poly_idx = 0;
- return ret;
-}
-
-void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
-{
- if (!poly1_result.poly || !poly2_result.poly)
- {
- return;
- }
- ConstPolygonRef poly1 = *poly1_result.poly;
- ConstPolygonRef poly2 = *poly2_result.poly;
- if (poly1.size() == 0 || poly2.size() == 0)
- {
- return;
- }
-
- Point center1 = poly1[0];
- ClosestPolygonPoint intermediate_poly2_result = findClosest(center1, poly2);
- ClosestPolygonPoint intermediate_poly1_result = findClosest(intermediate_poly2_result.p(), poly1);
-
- poly2_result = findClosest(intermediate_poly1_result.p(), poly2);
- poly1_result = findClosest(poly2_result.p(), poly1);
- walkToNearestSmallestConnection(poly1_result, poly2_result);
-}
-
void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
{
if (!poly1_result.isValid() || !poly2_result.isValid())
@@ -1510,14 +1422,14 @@ void PolygonUtils::fixSelfIntersections(const coord_t epsilon, Polygons& thiss)
return;
}
- const coord_t half_epsilon = (epsilon + 1) / 2;
+ const coord_t half_epsilon = std::max(10LL, (epsilon + 1) / 2);
// Points too close to line segments should be moved a little away from those line segments, but less than epsilon,
// so at least half-epsilon distance between points can still be guaranteed.
constexpr coord_t grid_size = 2000;
auto query_grid = PolygonUtils::createLocToLineGrid(thiss, grid_size);
- const coord_t move_dist = std::max(2LL, half_epsilon - 2);
+ const coord_t move_dist = half_epsilon - 2;
const coord_t half_epsilon_sqrd = half_epsilon * half_epsilon;
const size_t n = thiss.size();
@@ -1527,7 +1439,7 @@ void PolygonUtils::fixSelfIntersections(const coord_t epsilon, Polygons& thiss)
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx)
{
Point& pt = thiss[poly_idx][point_idx];
- for (const auto& line : query_grid->getNearby(pt, epsilon))
+ for (const auto& line : query_grid->getNearby(pt, epsilon * 2))
{
const size_t line_next_idx = (line.point_idx + 1) % thiss[line.poly_idx].size();
if (poly_idx == line.poly_idx && (point_idx == line.point_idx || point_idx == line_next_idx))
diff --git a/src/utils/polygonUtils.h b/src/utils/polygonUtils.h
index ef32a7344..489130bea 100644
--- a/src/utils/polygonUtils.h
+++ b/src/utils/polygonUtils.h
@@ -323,33 +323,6 @@ public:
static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, const ClosestPolygonPoint& closest_polygon_point, int preferred_dist_inside, const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function<int(Point)>& penalty_function = no_penalty_function);
/*!
- * Find a connecting line segment from one polygon to a collection of other polygons.
- *
- * This implementation uses a sparse grid to get to an accurate result quickly
- *
- * The first connection larger than \p min_connection_length and smaller than \p max_connection_length is returned.
- *
- * \param poly1 The polygon in which to search for a conection
- * \param polys2 The polygons to which to connect
- * \param min_connection_length The minimal conection length a connection needs to have in order to stop looking for other connections
- * \param max_connection_length The largest length of a connection to be found
- */
- static std::pair<ClosestPolygonPoint, ClosestPolygonPoint> findConnection(ConstPolygonRef poly1, Polygons& polys2, coord_t min_connection_length, coord_t max_connection_length, std::function<bool (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>)> precondition);
-
- /*!
- * Find the two points in two polygons with the smallest distance.
- *
- * The final connection will be close to the center of mass of the first polygon.
- *
- * \warning The ClosestPolygonPoint::poly fields output parameters should be initialized with the polygons for which to find the smallest connection.
- *
- * \param poly1_result Output parameter: the point at the one end of the smallest connection between its poly and \p poly2_result.poly.
- * \param poly2_result Output parameter: the point at the other end of the smallest connection between its poly and \p poly1_result.poly.
- * \param sample_size The number of points on each polygon to start the hill climbing search from. Use negative values for checking all combinations of points.
- */
- static void findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result);
-
- /*!
*
* \warning Assumes \p poly1_result and \p poly2_result have their pos and poly fields initialized!
*/
diff --git a/tests/ExtruderPlanTest.cpp b/tests/ExtruderPlanTest.cpp
index a8cca147a..e5b9b72d2 100644
--- a/tests/ExtruderPlanTest.cpp
+++ b/tests/ExtruderPlanTest.cpp
@@ -86,9 +86,10 @@ public:
{
const std::string mesh_id = "test_mesh";
constexpr Ratio flow_1 = 1.0_r;
+ constexpr Ratio width_1 = 1.0_r;
constexpr bool no_spiralize = false;
constexpr Ratio speed_1 = 1.0_r;
- square.assign({GCodePath(extrusion_config, mesh_id, SpaceFillType::PolyLines, flow_1, no_spiralize, speed_1)});
+ square.assign({GCodePath(extrusion_config, mesh_id, SpaceFillType::PolyLines, flow_1, width_1, no_spiralize, speed_1)});
square.back().points = {
Point(0, 0),
Point(1000, 0),
@@ -98,11 +99,11 @@ public:
};
lines.assign({
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1)
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1)
});
lines[0].points = {Point(0, 0), Point(1000, 0)};
lines[1].points = {Point(1000, 0), Point(1000, 400)};
@@ -114,11 +115,11 @@ public:
constexpr Ratio flow_08 = 0.8_r;
constexpr Ratio flow_04 = 0.4_r;
decreasing_flow.assign({
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_12, no_spiralize, speed_1),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_08, no_spiralize, speed_1),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_04, no_spiralize, speed_1)
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_12, width_1, no_spiralize, speed_1),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1 , width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_08, width_1, no_spiralize, speed_1),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1 , width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_04, width_1, no_spiralize, speed_1)
});
decreasing_flow[0].points = {Point(0, 0), Point(1000, 0)};
decreasing_flow[1].points = {Point(1000, 0), Point(1000, 400)};
@@ -130,11 +131,11 @@ public:
constexpr Ratio speed_08 = 0.8_r;
constexpr Ratio speed_04 = 0.4_r;
decreasing_speed.assign({
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_12),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_08),
- GCodePath(travel_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_04)
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_12),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_08),
+ GCodePath(travel_config , mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_04)
});
decreasing_speed[0].points = {Point(0, 0), Point(1000, 0)};
decreasing_speed[1].points = {Point(1000, 0), Point(1000, 400)};
@@ -143,12 +144,12 @@ public:
decreasing_speed[4].points = {Point(0, 800), Point(1000, 800)};
variable_width.assign({
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, 0.8_r, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, 0.6_r, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, 0.4_r, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, 0.2_r, no_spiralize, speed_1),
- GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, 0.0_r, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, 0.8_r, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, 0.6_r, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, 0.4_r, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, 0.2_r, no_spiralize, speed_1),
+ GCodePath(extrusion_config, mesh_id, SpaceFillType::Lines, flow_1, 0.0_r, no_spiralize, speed_1),
});
variable_width[0].points = {Point(0, 0), Point(1000, 0)};
variable_width[1].points = {Point(1000, 0), Point(2000, 0)};
@@ -194,18 +195,20 @@ public:
{}
/*!
- * Helper method to calculate the flow rate of a path in mm3 per second, ignoring any user specified speed alteration other than the back pressure compensation.
+ * Helper method to calculate the flow rate of a path in mm3 per second,
+ * minus the influence of flow rate and ignoring any user specified speed
+ * alteration other than the back pressure compensation.
* \param path The path to calculate the flow rate of.
- * \return The flow rate, in cubic millimeters per second (ignoring any user specified speed alteration other than back-pressure compensation).
+ * \return The flow rate, in cubic millimeters per second.
*/
- double calculatePathFlow(const GCodePath& path)
+ double calculatePathWidth(const GCodePath& path)
{
- return path.getExtrusionMM3perMM() * path.config->getSpeed() * path.speed_back_pressure_factor;
+ return path.getExtrusionMM3perMM() / path.config->getFlowRatio() / path.flow * path.config->getSpeed() * path.speed_back_pressure_factor;
}
bool shouldCountPath(const GCodePath& path) const
{
- return path.flow > 0.0 && path.config->getFlowRatio() > 0.0 && path.config->getLineWidth() > 0.0 && ! path.config->isTravelPath() && ! path.config->isBridgePath();
+ return path.flow > 0.0 && path.width_factor > 0.0 && path.config->getFlowRatio() > 0.0 && path.config->getLineWidth() > 0.0 && ! path.config->isTravelPath() && ! path.config->isBridgePath();
}
};
@@ -250,20 +253,20 @@ public:
TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationZeroIsUncompensated)
{
extruder_plan.paths = GetParam();
- std::vector<Ratio> original_flows;
+ std::vector<Ratio> original_widths;
std::vector<Ratio> original_speeds;
for(const GCodePath& path : extruder_plan.paths)
{
- original_flows.push_back(path.flow);
+ original_widths.push_back(path.width_factor);
original_speeds.push_back(path.speed_factor);
}
extruder_plan.applyBackPressureCompensation(0.0_r);
- ASSERT_EQ(extruder_plan.paths.size(), original_flows.size()) << "Number of paths may not have changed.";
+ ASSERT_EQ(extruder_plan.paths.size(), original_widths.size()) << "Number of paths may not have changed.";
for(size_t i = 0; i < extruder_plan.paths.size(); ++i)
{
- EXPECT_NEAR(original_flows[i], extruder_plan.paths[i].flow, error_margin) << "The flow rate did not change. Back pressure compensation doesn't adjust flow.";
+ EXPECT_NEAR(original_widths[i], extruder_plan.paths[i].width_factor, error_margin) << "The width did not change. Back pressure compensation doesn't adjust line width.";
EXPECT_NEAR(original_speeds[i], extruder_plan.paths[i].speed_factor, error_margin) << "The speed factor did not change, since the compensation factor was 0.";
}
}
@@ -285,7 +288,7 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull)
return;
}
//All flow rates must be equal to this one.
- const double first_flow_mm3_per_sec = calculatePathFlow(*first_extrusion);
+ const double first_flow_mm3_per_sec = calculatePathWidth(*first_extrusion);
for(GCodePath& path : extruder_plan.paths)
{
@@ -293,7 +296,7 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull)
{
continue; //Ignore travel moves.
}
- const double flow_mm3_per_sec = calculatePathFlow(path);
+ const double flow_mm3_per_sec = calculatePathWidth(path);
EXPECT_NEAR(flow_mm3_per_sec, first_flow_mm3_per_sec, error_margin) << "Every path must have a flow rate equal to the first, since the flow changes were completely compensated for.";
}
}
@@ -313,7 +316,7 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf)
{
continue; //Ignore travel moves.
}
- original_flows.push_back(calculatePathFlow(path));
+ original_flows.push_back(calculatePathWidth(path));
}
const double original_average = std::accumulate(original_flows.begin(), original_flows.end(), 0.0) / original_flows.size();
@@ -328,7 +331,7 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf)
{
continue; //Ignore travel moves.
}
- new_flows.push_back(calculatePathFlow(path));
+ new_flows.push_back(calculatePathWidth(path));
}
const double new_average = std::accumulate(new_flows.begin(), new_flows.end(), 0.0) / new_flows.size();
//Note that the new average doesn't necessarily need to be the same average! It is most likely a higher average in real-world scenarios.
@@ -337,7 +340,7 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf)
ASSERT_EQ(original_flows.size(), new_flows.size()) << "We need to have the same number of extrusion moves.";
for(size_t i = 0; i < new_flows.size(); ++i)
{
- EXPECT_NEAR((original_flows[i] - original_average) / 2.0, new_flows[i] - new_average, error_margin);
+ EXPECT_NEAR((original_flows[i] - original_average) / 2.0, new_flows[i] - new_average, error_margin) << "The differences in flow rate needs to be approximately halved, within margin of rounding errors.";
}
}
diff --git a/tests/InfillTest.cpp b/tests/InfillTest.cpp
index cde98a939..23b083f5e 100644
--- a/tests/InfillTest.cpp
+++ b/tests/InfillTest.cpp
@@ -172,7 +172,7 @@ namespace cura
); // There are some optional parameters, but these will do for now (future improvement?).
Settings infill_settings;
- VariableWidthPaths result_paths;
+ std::vector<VariableWidthLines> result_paths;
Polygons result_polygons;
Polygons result_lines;
infill.generate(result_paths, result_polygons, result_lines, infill_settings, nullptr, nullptr);
diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp
index a1dca462b..b57107aea 100644
--- a/tests/PathOrderMonotonicTest.cpp
+++ b/tests/PathOrderMonotonicTest.cpp
@@ -27,22 +27,22 @@ namespace cura
class PathOrderMonotonicTest : public testing::TestWithParam<std::tuple<std::string, AngleRadians>>
{};
- inline Point startVertex(const PathOrderMonotonic<ConstPolygonRef>::Path& path)
+ inline Point startVertex(const PathOrderPath<ConstPolygonPointer>& path)
{
- return path.vertices[path.start_vertex];
+ return (*path.vertices)[path.start_vertex];
}
- inline Point endVertex(const PathOrderMonotonic<ConstPolygonRef>::Path& path)
+ inline Point endVertex(const PathOrderPath<ConstPolygonPointer>& path)
{
- return path.vertices[path.vertices.size() - (1 + path.start_vertex)];
+ return (*path.vertices)[path.vertices->size() - (1 + path.start_vertex)];
}
- coord_t projectPathAlongAxis(const PathOrderMonotonic<ConstPolygonRef>::Path& path, const Point& vector)
+ coord_t projectPathAlongAxis(const PathOrderPath<ConstPolygonPointer>& path, const Point& vector)
{
return dot(startVertex(path), vector);
}
- coord_t projectEndAlongAxis(const PathOrderMonotonic<ConstPolygonRef>::Path& path, const Point& vector)
+ coord_t projectEndAlongAxis(const PathOrderPath<ConstPolygonPointer>& path, const Point& vector)
{
return dot(endVertex(path), vector);
}
@@ -58,8 +58,8 @@ namespace cura
coord_t shortestDistance
(
- const PathOrderMonotonic<ConstPolygonRef>::Path& path_a,
- const PathOrderMonotonic<ConstPolygonRef>::Path& path_b
+ const PathOrderPath<ConstPolygonPointer>& path_a,
+ const PathOrderPath<ConstPolygonPointer>& path_b
)
{
// NOTE: Assume these are more or less lines.
@@ -68,7 +68,7 @@ namespace cura
return vSize(point_pair.second - point_pair.first);
}
- coord_t pathLength(const PathOrderMonotonic<ConstPolygonRef>::Path& path)
+ coord_t pathLength(const PathOrderPath<ConstPolygonPointer>& path)
{
// NOTE: Assume these are more or less lines.
return vSize(endVertex(path) - startVertex(path));
@@ -114,7 +114,7 @@ namespace cura
max_deviation
);
Settings infill_settings;
- VariableWidthPaths result_paths;
+ std::vector<VariableWidthLines> result_paths;
Polygons dummy_polys;
infill_comp.generate(result_paths, dummy_polys, output, infill_settings, nullptr, nullptr);
}
@@ -127,7 +127,7 @@ namespace cura
const std::string& original_filename,
const AngleRadians& angle,
const Point& monotonic_vec,
- const std::vector<std::vector<PathOrderMonotonic<ConstPolygonRef>::Path>>& sections
+ const std::vector<std::vector<PathOrderPath<ConstPolygonPointer>>>& sections
)
{
constexpr int buff_size = 1024;
@@ -181,15 +181,15 @@ namespace cura
const Point perpendicular_axis{ turn90CCW(monotonic_axis) };
constexpr coord_t max_adjacent_distance = line_distance + 1;
- PathOrderMonotonic<ConstPolygonRef> object_under_test(angle_from_first_line, max_adjacent_distance, monotonic_axis * -1000);
+ PathOrderMonotonic<ConstPolygonPointer> object_under_test(angle_from_first_line, max_adjacent_distance, monotonic_axis * -1000);
for (const auto& polyline : polylines)
{
- object_under_test.addPolyline(polyline);
+ object_under_test.addPolyline(ConstPolygonPointer(polyline));
}
object_under_test.optimize();
// Collect sections:
- std::vector<std::vector<PathOrderMonotonic<ConstPolygonRef>::Path>> sections;
+ std::vector<std::vector<PathOrderPath<ConstPolygonPointer>>> sections;
sections.emplace_back();
coord_t last_path_mono_projection = projectPathAlongAxis(object_under_test.paths.front(), monotonic_axis);
for (const auto& path : object_under_test.paths)
diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp
index 925bf5a80..2210240ba 100644
--- a/tests/WallsComputationTest.cpp
+++ b/tests/WallsComputationTest.cpp
@@ -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 <gtest/gtest.h>
@@ -72,7 +72,6 @@ public:
//Settings for a simple 2 walls, about as basic as possible.
settings.add("alternate_extra_perimeter", "false");
- settings.add("beading_strategy_type", "inward_distributed");
settings.add("fill_outline_gaps", "false");
settings.add("initial_layer_line_width_factor", "100");
settings.add("magic_spiralize", "false");
diff --git a/tests/beading_strategy/CenterDeviationBeadingStrategyTest.cpp b/tests/beading_strategy/CenterDeviationBeadingStrategyTest.cpp
deleted file mode 100644
index 9816fee1a..000000000
--- a/tests/beading_strategy/CenterDeviationBeadingStrategyTest.cpp
+++ /dev/null
@@ -1,158 +0,0 @@
-//Copyright (c) 2021 Ultimaker B.V.
-//CuraEngine is released under the terms of the AGPLv3 or higher.
-
-#include <gtest/gtest.h>
-
-#include "../../src/BeadingStrategy/CenterDeviationBeadingStrategy.h" //Code under test.
-
-namespace cura
-{
-
-/*!
- * Tests calling the constructor of the CenterDeviationBeadingStrategy and
- * whether it sets members correctly.
- */
-TEST(CenterDeviationBeadingStrategy, Construction)
-{
- CenterDeviationBeadingStrategy strategy(400, 0.6, 0.5, 0.5); //Pretty standard settings.
- EXPECT_EQ(strategy.minimum_line_width_split, 200) << "Since the transition threshold was 50%, the minimum line width should be 0.5 times the preferred width.";
- strategy = CenterDeviationBeadingStrategy(400, 0.6, 0, 0);
- EXPECT_EQ(strategy.minimum_line_width_split, 0) << "Since the transition threshold was 0%, the minimum line width should be 0.";
- strategy = CenterDeviationBeadingStrategy(0, 0.6, 0.5, 0.5);
- EXPECT_EQ(strategy.minimum_line_width_split, 0) << "Since the line width was 0%, the minimum line width should also be 0.";
- strategy = CenterDeviationBeadingStrategy(400, 0.6, 1, 1);
- EXPECT_EQ(strategy.minimum_line_width_split, 400) << "Since the transition threshold was 100%, the minimum line width equals the preferred width.";
-}
-
-/*!
- * Tests getting the optimal thickness with Center Deviation.
- *
- * This should simply be a multiplication of the line width, when using Center
- * Deviation. It doesn't adjust the line widths by itself.
- */
-TEST(CenterDeviationBeadingStrategy, GetOptimalThickness)
-{
- constexpr coord_t line_width = 400;
- CenterDeviationBeadingStrategy strategy(line_width, 0.6, 0.5, 0.5);
- EXPECT_EQ(strategy.getOptimalThickness(0), 0) << "With 0 beads, you'll fill 0 space.";
- EXPECT_EQ(strategy.getOptimalThickness(1), line_width) << "With 1 bead, optimally you'll want to print that bead at the optimal line width.";
- EXPECT_EQ(strategy.getOptimalThickness(4), 4 * line_width) << "With 4 beads, optimally fill 4 line widths.";
-}
-
-/*!
- * Test getting the width at which we need to transition to a greater number of
- * lines.
- */
-TEST(CenterDeviationBeadingStrategy, GetTransitionThickness)
-{
- constexpr coord_t line_width = 400;
-
- //Transition ratio 25%.
- CenterDeviationBeadingStrategy strategy(line_width, 0.6, 0.25, 0.25);
- EXPECT_EQ(strategy.getTransitionThickness(0), 0.25 * line_width) << "The transition from 0 beads to 1 happens at 25% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(1), 1.25 * line_width) << "The transition from 1 bead to 2 happens at 125% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(4), 4.25 * line_width) << "The transition from 4 beads to 5 happens at 4 + 25% line width.";
-
- //Transition ratio 50%.
- strategy = CenterDeviationBeadingStrategy(line_width, 0.6, 0.5, 0.5);
- EXPECT_EQ(strategy.getTransitionThickness(0), 0.5 * line_width) << "The transition from 0 beads to 1 happens at 50% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(1), 1.5 * line_width) << "The transition from 1 bead to 2 happens at 150% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(5), 5.5 * line_width) << "The transition from 5 beads to 6 happens at 5 + 50% line width.";
-
- //Transition ratio 95%.
- strategy = CenterDeviationBeadingStrategy(line_width, 0.6, 0.95, 0.95);
- EXPECT_EQ(strategy.getTransitionThickness(0), 0.95 * line_width) << "The transition from 0 beads to 1 happens at 95% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(1), 1.95 * line_width) << "The transition from 1 bead to 2 happens at 195% line width.";
- EXPECT_EQ(strategy.getTransitionThickness(3), 3.95 * line_width) << "The transition from 3 beads to 4 happens at 3 + 95% line width.";
-
- //Transition ratio 100%.
- strategy = CenterDeviationBeadingStrategy(line_width, 0.6, 1, 1);
- EXPECT_EQ(strategy.getTransitionThickness(0), line_width) << "Only transition to have a line if it fits completely.";
- EXPECT_EQ(strategy.getTransitionThickness(1), 2 * line_width) << "Only transition to have two lines if they both fit completely.";
- EXPECT_EQ(strategy.getTransitionThickness(2), 3 * line_width) << "Only transition to have three lines if they all fit completely.";
-
- //Transition ratio 0%.
- strategy = CenterDeviationBeadingStrategy(line_width, 0.6, 0, 0);
- EXPECT_EQ(strategy.getTransitionThickness(0), 0) << "Always transition to 1 line. The minimum line width is 0 after all.";
- EXPECT_EQ(strategy.getTransitionThickness(1), line_width) << "If 1 line fits completely, immediately transition to 2 lines.";
- EXPECT_EQ(strategy.getTransitionThickness(6), 6 * line_width) << "If 6 lines fit completely, immediately transition to 7 lines.";
-}
-
-/*!
- * Test getting the optimal bead count for a given shape width.
- */
-TEST(CenterDeviationBeadingStrategy, GetOptimalBeadCount)
-{
- constexpr coord_t line_width = 400;
-
- //Transition ratio 25%.
- CenterDeviationBeadingStrategy strategy(line_width, 0.6, 0.25, 0.25);
- //Anything below 25% line width should then produce 0 lines.
- for(coord_t width = 0; width < 0.25 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 0) << "Width is below the transition thickness from 0 to 1 line, so it should produce 0 lines.";
- }
- EXPECT_LE(strategy.getOptimalBeadCount(0.25 * line_width), 1) << "At exactly the transition thickness, either 0 or 1 line is acceptable.";
- for(coord_t width = 0.25 * line_width + 1; width < 1.25 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 1) << "Width is above the transition thickness from 0 to 1 line but below 1 to 2 lines, so it should produce 1 line.";
- }
- EXPECT_TRUE(strategy.getOptimalBeadCount(1.25 * line_width) == 1 || strategy.getOptimalBeadCount(1.25 * line_width) == 2) << "At exactly 125% line width, either 1 or 2 lines is acceptable.";
- for(coord_t width = 1.25 * line_width + 1; width < 2.25 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 2) << "Width is above the transition thickness from 1 to 2 lines but below 2 to 3 lines, so it should produce 2 lines.";
- }
-
- //Transition ratio 80%.
- strategy = CenterDeviationBeadingStrategy(line_width, 0.6, 0.8, 0.8);
- //Anything below 80% line width should then produce 0 lines.
- for(coord_t width = 0; width < 0.8 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 0) << "Width is below the transition thickness from 0 to 1 line, so it should produce 0 lines.";
- }
- EXPECT_LE(strategy.getOptimalBeadCount(0.8 * line_width), 1) << "At exactly the transition thickness, either 0 or 1 line is acceptable.";
- for(coord_t width = 0.8 * line_width + 1; width < 1.8 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 1) << "Width is above the transition thickness from 0 to 1 line but below 1 to 2 lines, so it should produce 1 line.";
- }
- EXPECT_TRUE(strategy.getOptimalBeadCount(1.8 * line_width) == 1 || strategy.getOptimalBeadCount(1.8 * line_width) == 2) << "At exactly 180% line width, either 1 or 2 lines is acceptable.";
- for(coord_t width = 1.8 * line_width + 1; width < 2.8 * line_width; width += 10)
- {
- EXPECT_EQ(strategy.getOptimalBeadCount(width), 2) << "Width is above the transition thickness from 1 to 2 lines but below 2 to 3 lines, so it should produce 2 lines.";
- }
-}
-
-/*!
- * Tests whether the line compactness setting does what it is supposed to do,
- * producing fewer, wider lines when the setting is high than when the setting
- * is low.
- *
- * This is a test for requirements. The exact outcome of a function is not
- * tested, but properties of the outcome is tested.
- */
-TEST(CenterDeviationBeadingStrategy, LineCompactnessMonotonic)
-{
- constexpr coord_t line_width = 400;
- constexpr coord_t widths[] = {0, 1, 99, 101, 150, 200, 299, 300, 301, 399, 400, 401, 410, 450, 500, 660, 770, 880, 910, 1000, 1200}; //Bunch of widths to test with.
- constexpr float compactnesses[] = {0, 0.1, 0.2, 0.24, 0.25, 0.26, 0.3, 0.5, 0.7, 0.75, 0.99, 1}; //Bunch of line compactness factors to test with.
- constexpr size_t num_compactnesses = sizeof(compactnesses) / sizeof(float);
-
- for(coord_t width : widths)
- {
- for(size_t low_index = 0; low_index < num_compactnesses; ++low_index)
- {
- const float low_compactness = compactnesses[low_index];
- for(size_t high_index = low_index; high_index < num_compactnesses; ++high_index)
- {
- const float high_compactness = compactnesses[high_index];
-
- EXPECT_GE(
- CenterDeviationBeadingStrategy(line_width, 0.6, low_compactness, low_compactness).getOptimalBeadCount(width),
- CenterDeviationBeadingStrategy(line_width, 0.6, high_compactness, high_compactness).getOptimalBeadCount(width)
- ) << "When the compactness is low, the number of beads should always be greater or equal to when the compactness is high.";
- }
- }
- }
-}
-
-} \ No newline at end of file
diff --git a/tests/utils/ExtrusionLineTest.cpp b/tests/utils/ExtrusionLineTest.cpp
index d26820a98..100ae790f 100644
--- a/tests/utils/ExtrusionLineTest.cpp
+++ b/tests/utils/ExtrusionLineTest.cpp
@@ -8,6 +8,7 @@
#include <../src/utils/linearAlg2D.h>
#include <limits>
+#include <numeric>
namespace cura
{
@@ -405,36 +406,39 @@ namespace cura
TEST(ExtrusionLineTest, simplifyLineWidthVariance)
{
//Generate a line with several vertices halfway.
- constexpr coord_t spacing = 100;
- ExtrusionLine colinear_polylines(0, false);;
+ ExtrusionLine colinear_polylines(0, false);
auto& colinear = colinear_polylines.junctions;
colinear.emplace_back(Point(0, 0), 200, 0);
- colinear.emplace_back(Point(spacing / 4, 0), 200, 0);
- colinear.emplace_back(Point(spacing / 2, 0), 400, 0);
- colinear.emplace_back(Point(spacing / 2 + spacing / 4, 0), 600, 0);
- colinear.emplace_back(Point(spacing, 0), 800, 0);
+ colinear.emplace_back(Point(500, 0), 200, 0);
+ colinear.emplace_back(Point(1000, 0), 400, 0);
+ colinear.emplace_back(Point(1500, 0), 600, 0);
+ colinear.emplace_back(Point(2000, 0), 800, 0);
+
+ // Get 'before' average width:
+ coord_t averge_width_before = 0;
+ for (size_t i_junction = 0; i_junction < (colinear.size() - 1); ++i_junction)
+ {
+ averge_width_before += ((colinear[i_junction].w + colinear[i_junction + 1].w) / 2) * vSize(colinear[i_junction].p - colinear[i_junction + 1].p);
+ }
- colinear_polylines.simplify(25 * 25, 25 * 25, std::numeric_limits<coord_t>::max());
+ colinear_polylines.simplify(20 * 20, 5 * 5, std::numeric_limits<coord_t>::max()); //Regardless of parameters, it should always remove those middle vertices.
ASSERT_EQ(colinear_polylines.junctions.size(), 2) << "The degenerate vertices should have been removed.";
- ASSERT_EQ(colinear[1].w, 500) << "The width of the end-junction should be the average of all removed."; // Since the distances where equal, and they should all have been merged with that one.
}
TEST(ExtrusionLineTest, simplifyNoLineWidthVariance)
{
//Generate a line with several vertices halfway.
- constexpr coord_t spacing = 100;
- ExtrusionLine colinear_polylines(0, false);;
+ ExtrusionLine colinear_polylines(0, false);
auto& colinear = colinear_polylines.junctions;
colinear.emplace_back(Point(0, 0), 200, 0);
- colinear.emplace_back(Point(spacing / 4, 0), 200, 0);
- colinear.emplace_back(Point(spacing / 2, 0), 400, 0);
- colinear.emplace_back(Point(spacing / 2 + spacing / 4, 0), 600, 0);
- colinear.emplace_back(Point(spacing, 0), 800, 0);
+ colinear.emplace_back(Point(500, 0), 200, 0);
+ colinear.emplace_back(Point(1000, 0), 400, 0);
+ colinear.emplace_back(Point(1500, 0), 600, 0);
+ colinear.emplace_back(Point(2000, 0), 800, 0);
- colinear_polylines.simplify(25 * 25, 25 * 25, 1);
+ colinear_polylines.simplify(20 * 20, 5 * 5, 1); // Now it should _not_ remove the vertices, since the total width altered will be more than the max area ...
ASSERT_EQ(colinear_polylines.junctions.size(), 5) << "No junctions should have been removed."; // ... even though they are co-linear!
- ASSERT_EQ(colinear.back().w, 800) << "The width of the end-junction should not have been changed.";
}
}
diff --git a/tests/utils/PolygonConnectorTest.cpp b/tests/utils/PolygonConnectorTest.cpp
index bda3a9d89..0d4d8c920 100644
--- a/tests/utils/PolygonConnectorTest.cpp
+++ b/tests/utils/PolygonConnectorTest.cpp
@@ -1,4 +1,4 @@
-//Copyright (c) 2019 Ultimaker B.V.
+//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <gtest/gtest.h>
@@ -14,14 +14,16 @@ class PolygonConnectorTest : public testing::Test
{
public:
Polygon test_square;
- Polygon test_square2; //Larger and more to the right.
+ Polygon test_square_around; //Larger, around the first square.
+ Polygon test_square_adjacent; //Next to the first square.
Polygon test_triangle;
Polygon test_circle;
Polygon test_convex_shape;
Polygons test_shapes; //All above polygons! As well as an inset of 100 microns of them.
PolygonConnector* pc;
- Polygons connecteds;
+ Polygons connected_polygons;
+ std::vector<VariableWidthLines> connected_paths;
virtual void SetUp()
{
@@ -29,50 +31,19 @@ public:
test_square.emplace_back(1000, 0);
test_square.emplace_back(1000, 1000);
test_square.emplace_back(0, 1000);
- test_shapes.add(test_square);
- test_square2.emplace_back(1100, 1500);
- test_square2.emplace_back(2000, 1500);
- test_square2.emplace_back(2000, -500);
- test_square2.emplace_back(1100, -500);
- test_shapes.add(test_square2);
-
- test_triangle.emplace_back(0, 2100);
- test_triangle.emplace_back(500, 1100);
- test_triangle.emplace_back(1500, 2100);
- test_shapes.add(test_triangle);
-
- for (double a = 0; a < 1.0; a += 0.05)
- {
- test_circle.add(Point(2050, 2050) + Point(std::cos(a * 2 * M_PI)*500, std::sin(a * 2 * M_PI)*500));
- }
- test_shapes.add(test_circle);
-
- test_convex_shape.emplace_back(-300, 0);
- test_convex_shape.emplace_back(-100, 500);
- test_convex_shape.emplace_back(-100, 600);
- test_convex_shape.emplace_back(-200, 1000);
- test_convex_shape.emplace_back(-500, 1500);
- test_convex_shape.emplace_back(-1500, 1500);
- test_convex_shape.emplace_back(-1500, 1500);
- test_convex_shape.emplace_back(-1600, 1100);
- test_convex_shape.emplace_back(-700, 200);
- test_shapes.add(test_convex_shape);
-
- Polygons inset = test_shapes;
- while (!inset.empty())
- {
- inset = inset.offset(-100);
- test_shapes.add(inset);
- }
+ test_square_around.emplace_back(1100, 1100);
+ test_square_around.emplace_back(-100, 1100);
+ test_square_around.emplace_back(-100, -100);
+ test_square_around.emplace_back(1100, -100);
- constexpr coord_t line_width = 100;
- constexpr coord_t max_dist = 170;
- pc = new PolygonConnector(line_width, max_dist);
- pc->add(test_shapes);
- connecteds = pc->connect();
+ test_square_adjacent.emplace_back(1100, 200);
+ test_square_adjacent.emplace_back(2100, 200);
+ test_square_adjacent.emplace_back(2100, 1200);
+ test_square_adjacent.emplace_back(1100, 1200);
- ASSERT_GT(connecteds.size(), 0) << "PolygonConnector gave no output polygons!";
+ constexpr coord_t line_width = 100;
+ pc = new PolygonConnector(line_width);
}
void TearDown()
@@ -82,65 +53,126 @@ public:
};
-TEST_F(PolygonConnectorTest, getBridgeTest)
+/*!
+ * Test creating a bridge between two squares that are nested in each other at
+ * precisely the line width apart.
+ *
+ * This is a common occurrence with skin.
+ */
+TEST_F(PolygonConnectorTest, getBridgeNestedSquares)
{
- PolygonConnector::PolygonBridge predicted(
- PolygonConnector::PolygonConnection{
- ClosestPolygonPoint(Point(0, 500), 2, test_square),
- ClosestPolygonPoint(Point(-100, 500), 0, test_convex_shape)},
- PolygonConnector::PolygonConnection{
- ClosestPolygonPoint(Point(0, 600), 2, test_square),
- ClosestPolygonPoint(Point(-100, 600), 0, test_convex_shape)});
- std::vector<Polygon> polys;
- polys.push_back(test_convex_shape);
-
- std::optional<PolygonConnector::PolygonBridge> computed = pc->getBridge(test_square, polys);
-
- ASSERT_TRUE(bool(computed)) << "An answer is expected.";
- if (computed)
- {
- const coord_t a_from_error = vSize(predicted.a.from.p() - computed->a.from.p());
- const coord_t a_to_error = vSize(predicted.a.to.p() - computed->a.to.p());
- const coord_t b_from_error = vSize(predicted.b.from.p() - computed->b.from.p());
- const coord_t b_to_error = vSize(predicted.b.to.p() - computed->b.to.p());
-
- constexpr coord_t maximum_error = 10;
- EXPECT_LT(a_from_error, maximum_error) << "a.from was computed to something different from what was predicted!";
- EXPECT_LT(a_to_error, maximum_error) << "a.to was computed to something different from what was predicted!";
- EXPECT_LT(b_from_error, maximum_error) << "b.from was computed to something different from what was predicted!";
- EXPECT_LT(b_to_error, maximum_error) << "b.to was computed to something different from what was predicted!";
- }
+ std::vector<Polygon> to_connect({test_square_around});
+ std::optional<PolygonConnector::PolygonBridge<Polygon>> bridge = pc->getBridge(test_square, to_connect);
+
+ ASSERT_NE(bridge, std::nullopt) << "The two polygons are nested simply, so they are definitely positioned closely enough to bridge. They are also wide enough.";
+
+ EXPECT_EQ(vSize(bridge->a.from_point - bridge->a.to_point), 100) << "The polygons are 100 units spaced out concentrically, so this is the shortest possible bridge.";
+ EXPECT_EQ(vSize(bridge->b.from_point - bridge->b.to_point), 100) << "The second bridge should also be equally short in this case.";
+ EXPECT_EQ(LinearAlg2D::getDist2BetweenLineSegments(bridge->a.from_point, bridge->a.to_point, bridge->b.from_point, bridge->b.to_point), 100 * 100) << "The bridges should be spaced 1 line width (100 units) apart.";
+ EXPECT_LT(LinearAlg2D::pointIsLeftOfLine(bridge->b.from_point, bridge->a.from_point, bridge->a.to_point), 0) << "Connection B should be to the right of connection A.";
+ EXPECT_LT(LinearAlg2D::pointIsLeftOfLine(bridge->b.to_point, bridge->a.from_point, bridge->a.to_point), 0) << "Connection B should be to the right of connection A.";
}
-TEST_F(PolygonConnectorTest, connectionLengthTest)
+/*!
+ * Test creating a bridge between two adjacent squares that are spaced 1 line
+ * width apart.
+ *
+ * This is a common occurrence with multiplied infill.
+ */
+TEST_F(PolygonConnectorTest, getBridgeAdjacentSquares)
{
- constexpr coord_t maximum_distance = 170;
- std::unordered_set<Point> input_verts;
- for (ConstPolygonRef poly : test_shapes)
- {
- for (Point p : poly)
- {
- input_verts.emplace(p);
- }
- }
+ std::vector<Polygon> to_connect({test_square_adjacent});
+ std::optional<PolygonConnector::PolygonBridge<Polygon>> bridge = pc->getBridge(test_square, to_connect);
- coord_t longest_connection_dist = 0;
- size_t too_long_connection_count = 0;
- for (PolygonConnector::PolygonBridge bridge : pc->all_bridges)
- {
- for (auto connection : {bridge.a, bridge.b})
- {
- const coord_t connection_dist = vSize(connection.to.p() - connection.from.p());
- if (connection_dist > maximum_distance)
- {
- too_long_connection_count++;
- longest_connection_dist = std::max(longest_connection_dist, connection_dist);
- }
- }
- }
+ ASSERT_NE(bridge, std::nullopt) << "The two polygons are adjacent, spaced closely enough to bridge and with enough room.";
+
+ EXPECT_EQ(vSize(bridge->a.from_point - bridge->a.to_point), 100) << "The polygons are 100 units spaced apart, so this is the shortest possible bridge.";
+ EXPECT_EQ(vSize(bridge->b.from_point - bridge->b.to_point), 100) << "The second bridge should also be equally short in this case.";
+ EXPECT_EQ(LinearAlg2D::getDist2BetweenLineSegments(bridge->a.from_point, bridge->a.to_point, bridge->b.from_point, bridge->b.to_point), 100 * 100) << "The bridges should be spaced 1 line width (100 units) apart.";
+ EXPECT_LT(LinearAlg2D::pointIsLeftOfLine(bridge->b.from_point, bridge->a.from_point, bridge->a.to_point), 0) << "Connection B should be to the right of connection A.";
+ EXPECT_LT(LinearAlg2D::pointIsLeftOfLine(bridge->b.to_point, bridge->a.from_point, bridge->a.to_point), 0) << "Connection B should be to the right of connection A.";
+}
+
+/*!
+ * Test that the bridge is created in the location where the polygons are
+ * closest together.
+ */
+TEST_F(PolygonConnectorTest, getBridgeClosest)
+{
+ Polygon adjacent_slanted; //A polygon that's adjacent to the first square, but tilted such that the vertex at [1100,200] is definitely the closest.
+ adjacent_slanted.emplace_back(1100, 200);
+ adjacent_slanted.emplace_back(2100, 200);
+ adjacent_slanted.emplace_back(2140, 1200);
+ adjacent_slanted.emplace_back(1140, 1200);
+ std::vector<Polygon> to_connect({adjacent_slanted});
- ASSERT_EQ(too_long_connection_count, 0) << "PolygonConnector::connect() obtained " << too_long_connection_count << " too long bridge connections! Longest is " << INT2MM(longest_connection_dist) << "\n";
+ std::optional<PolygonConnector::PolygonBridge<Polygon>> bridge = pc->getBridge(test_square, to_connect);
+
+ ASSERT_NE(bridge, std::nullopt) << "The two polygons are adjacent and spaced closely enough to bridge along their entire side, even with the slant.";
+
+ EXPECT_EQ(bridge->b.from_point, Point(1000, 200)) << "The closest connection is [1000,200] -> [1100,200]. There is no space to the right of that, so bridge B should be there.";
+ EXPECT_EQ(bridge->b.to_point, Point(1100, 200)) << "The closest connection is [1000,200] -> [1100,200]. There is no space to the right of that, so bridge B should be there.";
+ EXPECT_GT(LinearAlg2D::pointIsLeftOfLine(bridge->a.from_point, bridge->b.from_point, bridge->b.to_point), 0) << "Connection A should be to the left of connection B.";
+ EXPECT_GT(LinearAlg2D::pointIsLeftOfLine(bridge->a.to_point, bridge->b.from_point, bridge->b.to_point), 0) << "Connection A should be to the left of connection B.";
+}
+
+/*!
+ * Test attempting to create a bridge when the polygons are too far apart.
+ */
+TEST_F(PolygonConnectorTest, getBridgeTooFar)
+{
+ Polygon too_far; //More than 1.5 line width away.
+ too_far.emplace_back(1200, 0);
+ too_far.emplace_back(2200, 0);
+ too_far.emplace_back(2200, 1000);
+ too_far.emplace_back(1200, 1000);
+ std::vector<Polygon> to_connect({too_far});
+
+ std::optional<PolygonConnector::PolygonBridge<Polygon>> bridge = pc->getBridge(test_square, to_connect);
+
+ EXPECT_EQ(bridge, std::nullopt) << "The two polygons are 200 units apart where they are closest, which is more than 1.5 times the line width (100), so they can't be connected.";
}
+/*!
+ * Test attempting to create a bridge when the connecting part is too narrow.
+ *
+ * Since the bridging lines need to be spaced 1 line width apart, they can't be
+ * too close together. If the bridge can't be constructed keeping proper spacing
+ * the bridge should fail to be created.
+ */
+TEST_F(PolygonConnectorTest, getBridgeTooNarrow)
+{
+ Polygon too_narrow;
+ too_narrow.emplace_back(1100, 400);
+ too_narrow.emplace_back(2100, 400);
+ too_narrow.emplace_back(2100, 480); //Less than 100 units wide.
+ too_narrow.emplace_back(1100, 480);
+ std::vector<Polygon> to_connect({too_narrow});
+
+ std::optional<PolygonConnector::PolygonBridge<Polygon>> bridge = pc->getBridge(test_square, to_connect);
+
+ EXPECT_EQ(bridge, std::nullopt) << "Where the two polygons are adjacent is only 80 units wide. This is not enough to create a bridge with the connecting lines spaced 1 line width (100 units) apart.";
+}
+
+/*!
+ * Try connecting four nested polygons.
+ *
+ * Let's play a game of connect four!
+ */
+TEST_F(PolygonConnectorTest, connectFourNested)
+{
+ Polygons connecting;
+ connecting.add(test_square_around); //1200-wide square.
+ connecting.add(test_square); //1000-wide square.
+ connecting.add(test_square.offset(-100)); //800-wide square.
+ connecting.add(test_square.offset(-200)); //600-wide square.
+
+ pc->add(connecting);
+ Polygons output_polygons;
+ std::vector<VariableWidthLines> output_paths;
+ pc->connect(output_polygons, output_paths);
+
+ EXPECT_EQ(output_polygons.size(), 1) << "All four polygons should've gotten connected into 1 single polygon.";
+}
}