1 //Copyright (c) 2019 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3
4 #include <gtest/gtest.h>
5
6 #include "../src/Application.h" //To set up a scene and load settings that the layer plan and merger need.
7 #include "../src/FanSpeedLayerTime.h" //Required to construct a layer plan. Doesn't influence our tests.
8 #include "../src/pathPlanning/GCodePath.h" //The paths that we're going to be merging.
9 #include "../src/LayerPlan.h" //Constructing plans that the mergers can merge lines in.
10 #include "../src/MergeInfillLines.h" //The class under test.
11 #include "../src/RetractionConfig.h" //Required to construct a layer plan. Doesn't influence our tests.
12 #include "../src/Slice.h" //To set up a scene and load settings that the layer plan and merger need.
13 #include "../src/settings/types/LayerIndex.h" //Required to construct a layer plan. Doesn't influence our tests.
14
15 namespace cura
16 {
17
18 class MergeInfillLinesTest : public testing::Test
19 {
20 public:
21 //These settings don't matter for this test so they are all the same for every fixture.
22 const size_t extruder_nr = 0;
23 const LayerIndex layer_nr = 0;
24 const bool is_initial_layer = false;
25 const bool is_raft_layer = false;
26 const coord_t layer_thickness = 100;
27 const Point starting_position; //All plans start at 0,0.
28
29 /*
30 * A merger to test with.
31 */
32 ExtruderPlan* extruder_plan;
33 MergeInfillLines* merger;
34
35 /*
36 * These fields are required for constructing layer plans and must be held
37 * constant for as long as the lifetime of the plans. Construct them once
38 * and store them in this fixture class.
39 */
40 const FanSpeedLayerTimeSettings fan_speed_layer_time;
41 const RetractionConfig retraction_config;
42 const GCodePathConfig skin_config;
43 const GCodePathConfig travel_config;
44
45 /*
46 * A path of skin lines without any points.
47 */
48 GCodePath empty_skin;
49
50 /*
51 * A path of a single skin line.
52 */
53 GCodePath single_skin;
54
55 /*
56 * A path of multiple skin lines that together form a straight line.
57 *
58 * This path should not get merged together to a single line.
59 */
60 GCodePath lengthwise_skin;
61
62 /*
63 * Basic zigzag of skin lines.
64 * There are 11 lines with travel moves in between them. The lines are 100
65 * microns long and 400 microns wide. They should get merged to one long
66 * line of 100 microns wide and 4400 microns long.
67 */
68 std::vector<GCodePath> zigzag;
69
MergeInfillLinesTest()70 MergeInfillLinesTest()
71 : starting_position(0, 0)
72 , fan_speed_layer_time()
73 , retraction_config()
74 , skin_config(PrintFeatureType::Skin, 400, layer_thickness, 1, GCodePathConfig::SpeedDerivatives{50, 1000, 10})
75 , travel_config(PrintFeatureType::MoveCombing, 0, layer_thickness, 0, GCodePathConfig::SpeedDerivatives{100, 1000, 10})
76 , empty_skin(skin_config, "merge_infill_lines_mesh", SpaceFillType::None, 1.0, false)
77 , single_skin(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, 1.0, false)
78 , lengthwise_skin(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, 1.0, false)
79 {
80 single_skin.points.emplace_back(1000, 0);
81
82 lengthwise_skin.points = {Point(1000, 0),
83 Point(2000, 0),
84 Point(3000, 0),
85 Point(4000, 0)};
86
87 //Create the zigzag.
88 constexpr Ratio normal_flow = 1.0;
89 constexpr bool no_spiralize = false;
90 constexpr size_t num_lines = 10;
91 //Creates a zig-zag line with extrusion moves when moving in the Y direction and travel moves in between:
92 // _ _ _
93 // | |_| |_| |_|
94 for(size_t i = 0; i < num_lines; i++)
95 {
96 zigzag.emplace_back(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, normal_flow, no_spiralize);
97 zigzag.back().points.emplace_back(400 * i, 100 * ((i + 1) % 2));
98 zigzag.emplace_back(travel_config, "merge_infill_lines_mesh", SpaceFillType::None, normal_flow, no_spiralize);
99 zigzag.back().points.emplace_back(400 * (i + 1), 100 * ((i + 1) % 2));
100 }
101 //End with an extrusion move, not a travel move.
102 zigzag.emplace_back(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, normal_flow, no_spiralize);
103 zigzag.back().points.emplace_back(400 * num_lines, 100 * ((num_lines + 1) % 2));
104 }
105
SetUp()106 void SetUp()
107 {
108 //Set up a scene so that we may request settings.
109 Application::getInstance().current_slice = new Slice(1);
110 Application::getInstance().current_slice->scene.extruders.emplace_back(0, nullptr);
111 ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders.back();
112 train.settings.add("machine_nozzle_size", "0.4");
113 train.settings.add("meshfix_maximum_deviation", "0.1");
114
115 extruder_plan = new ExtruderPlan(extruder_nr, layer_nr, is_initial_layer, is_raft_layer, layer_thickness, fan_speed_layer_time, retraction_config);
116 merger = new MergeInfillLines(*extruder_plan);
117 }
118
TearDown()119 void TearDown()
120 {
121 delete extruder_plan;
122 delete merger;
123 delete Application::getInstance().current_slice;
124 }
125 };
126
TEST_F(MergeInfillLinesTest,CalcPathLengthEmpty)127 TEST_F(MergeInfillLinesTest, CalcPathLengthEmpty)
128 {
129 EXPECT_EQ(0, merger->calcPathLength(starting_position, empty_skin));
130 }
131
TEST_F(MergeInfillLinesTest,CalcPathLengthSingle)132 TEST_F(MergeInfillLinesTest, CalcPathLengthSingle)
133 {
134 EXPECT_EQ(1000, merger->calcPathLength(starting_position, single_skin));
135 }
136
TEST_F(MergeInfillLinesTest,CalcPathLengthMultiple)137 TEST_F(MergeInfillLinesTest, CalcPathLengthMultiple)
138 {
139 EXPECT_EQ(4000, merger->calcPathLength(starting_position, lengthwise_skin));
140 }
141
142 /*
143 * Tries merging an empty set of paths together.
144 *
145 * This changes nothing in the paths, since there is nothing to change.
146 */
TEST_F(MergeInfillLinesTest,MergeEmpty)147 TEST_F(MergeInfillLinesTest, MergeEmpty)
148 {
149 std::vector<GCodePath> paths; //Empty. No paths to merge.
150
151 const bool result = merger->mergeInfillLines(paths, starting_position);
152
153 EXPECT_FALSE(result) << "There are no lines to merge.";
154 EXPECT_EQ(paths.size(), 0) << "The number of paths should still be zero.";
155 }
156
157 /*
158 * Tries merging a single path of a single line.
159 *
160 * This changes nothing in the paths, since the line cannot be merged with
161 * anything else.
162 */
TEST_F(MergeInfillLinesTest,MergeSingle)163 TEST_F(MergeInfillLinesTest, MergeSingle)
164 {
165 std::vector<GCodePath> paths;
166 paths.push_back(single_skin);
167
168 const bool result = merger->mergeInfillLines(paths, starting_position);
169
170 EXPECT_FALSE(result) << "There is only one line, so it can't be merged with other lines.";
171 ASSERT_EQ(paths.size(), 1) << "The path should not get removed.";
172 EXPECT_EQ(paths[0].points.size(), 1) << "The path should not be modified.";
173 }
174
175 /*
176 * Tries merging a single path that consists of multiple vertices in a straight
177 * line.
178 *
179 * This should not change anything in the paths, since the lines are in a single
180 * path without travel moves in between. It's just drawing a curve, and that
181 * curve should not get modified.
182 *
183 * This is basically the case that went wrong with the "Weird Fat Infill" bug
184 * (CURA-5776).
185 */
TEST_F(MergeInfillLinesTest,MergeLenthwise)186 TEST_F(MergeInfillLinesTest, MergeLenthwise)
187 {
188 std::vector<GCodePath> paths;
189 paths.push_back(lengthwise_skin);
190
191 const bool result = merger->mergeInfillLines(paths, starting_position);
192
193 EXPECT_FALSE(result) << "Patterns like Gyroid infill with many (almost) lengthwise lines should not get merged, even if those lines are short.";
194 ASSERT_EQ(paths.size(), 1) << "The path should not get removed or split.";
195 EXPECT_EQ(paths[0].points.size(), 4) << "The path should not be modified.";
196 }
197
198 /*
199 * Tries merging a bunch of parallel lines with travel moves in between.
200 *
201 * This is the basic use case for merging infill lines.
202 */
TEST_F(MergeInfillLinesTest,MergeParallel)203 TEST_F(MergeInfillLinesTest, MergeParallel)
204 {
205 const bool result = merger->mergeInfillLines(zigzag, starting_position);
206
207 EXPECT_TRUE(result) << "The simple zig-zag pattern should get merged fine.";
208 EXPECT_LE(zigzag.size(), 5); //Some lenience. Ideally it'd be one.
209 }
210
211 /*
212 * Tests if the total extruded volume is the same as the original lines.
213 */
TEST_F(MergeInfillLinesTest,DISABLED_ExtrudedVolume)214 TEST_F(MergeInfillLinesTest, DISABLED_ExtrudedVolume)
215 {
216 coord_t original_volume = 0;
217 Point position = starting_position;
218 for(const GCodePath& path : zigzag)
219 {
220 for(const Point& point : path.points)
221 {
222 const coord_t length = vSize(point - position);
223 original_volume += length * (path.getExtrusionMM3perMM() * 1000000);
224 position = point;
225 }
226 }
227
228 merger->mergeInfillLines(zigzag, starting_position);
229 /* If it fails to merge, other tests fail. This test depends on that, but we
230 don't necessarily want it to fail as a false negative if merging in general
231 fails, because we don't want to think that the volume is wrong then. So we
232 don't check the outcome of the merging itself, just the volume. */
233
234 coord_t new_volume = 0;
235 position = starting_position;
236 for(const GCodePath& path : zigzag)
237 {
238 for(const Point& point : path.points)
239 {
240 const coord_t length = vSize(point - position);
241 new_volume += length * (path.getExtrusionMM3perMM() * 1000000);
242 position = point;
243 }
244 }
245
246 EXPECT_EQ(original_volume, new_volume);
247 }
248
249 /*
250 * Parameterised test for merging infill lines inside thin walls.
251 *
252 * The thin walls are filled with lines of various rotations. This is the float
253 * parameter.
254 */
255 class MergeInfillLinesThinWallsTest : public MergeInfillLinesTest, public testing::WithParamInterface<double>
256 {
257 public:
258 const coord_t wall_thickness = 200; //0.2mm wall.
259 };
260
TEST_P(MergeInfillLinesThinWallsTest,DISABLED_MergeThinWalls)261 TEST_P(MergeInfillLinesThinWallsTest, DISABLED_MergeThinWalls)
262 {
263 const AngleRadians rotation = AngleRadians(GetParam()); //Converts degrees to radians!
264 constexpr Ratio normal_flow = 1.0;
265 constexpr bool no_spiralize = false;
266 constexpr size_t num_lines = 10;
267
268 //Construct parallel lines under a certain rotation. It looks like this: /////
269 //The perpendicular distance between the lines will be exactly one line width.
270 //The distance between the adjacent endpoints of the lines will be greater for steeper angles.
271 std::vector<GCodePath> paths;
272 const coord_t line_shift = wall_thickness / std::cos(rotation); //How far the top of the line is shifted from the bottom.
273 const coord_t line_horizontal_spacing = skin_config.getLineWidth() / std::sin(rotation); //Horizontal spacing between starting vertices.
274 for(size_t i = 0; i < num_lines; i++)
275 {
276 paths.emplace_back(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, normal_flow, no_spiralize);
277 paths.back().points.emplace_back(line_horizontal_spacing * i + line_shift * ((i + 1) % 2), wall_thickness * ((i + 1) % 2));
278 paths.emplace_back(travel_config, "merge_infill_lines_mesh", SpaceFillType::None, normal_flow, no_spiralize);
279 paths.back().points.emplace_back(line_horizontal_spacing * (i + 1) + line_shift * ((i + 1) % 2), wall_thickness * ((i + 1) % 2));
280 }
281 paths.emplace_back(skin_config, "merge_infill_lines_mesh", SpaceFillType::Lines, normal_flow, no_spiralize);
282 paths.back().points.emplace_back(line_horizontal_spacing * num_lines + line_shift * ((num_lines + 1) % 2), wall_thickness * ((num_lines + 1) % 2));
283
284 const bool merged = merger->mergeInfillLines(paths, starting_position);
285
286 EXPECT_TRUE(merged) << "These are the test cases where line segments should get merged.";
287 EXPECT_LE(paths.size(), 5) << "Should get merged to 1 line, but give a bit of leeway.";
288 }
289
290 INSTANTIATE_TEST_CASE_P(MergeThinWallsTest, MergeInfillLinesThinWallsTest, testing::Range(-45.0, 45.0, 5.0));
291
292 } //namespace cura