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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ on:
- '[0-9].[0-9][0-9]*'

jobs:
check_actor:
uses: ultimaker/cura-workflows/.github/workflows/check-actor.yml@main
secrets: inherit

conan-package:
needs: [check_actor]
if: needs.check_actor.outputs.proceed == 'true'
uses: ultimaker/cura-workflows/.github/workflows/conan-package.yml@main
with:
platform_wasm: true
Expand Down
1 change: 1 addition & 0 deletions include/ExtruderPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ExtruderPlan
friend class LayerPlanBuffer;
friend class LayerPlan;
#ifdef BUILD_TESTS
friend class LayerPlanTest;
friend class ExtruderPlanPathsParameterizedTest;
FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationZeroIsUncompensated);
FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull);
Expand Down
7 changes: 7 additions & 0 deletions include/LayerPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class LayerPlan : public NoCopy
friend class LayerPlanBuffer;
#ifdef BUILD_TESTS
friend class AddTravelTest;
friend class LayerPlanTest;
friend class DISABLED_FffGcodeWriterTest_SurfaceGetsExtraInfillLinesUnderIt_Test;
friend class AntiOozeAmountsTest;
FRIEND_TEST(AntiOozeAmountsTest, ComputeAntiOozeAmounts);
Expand Down Expand Up @@ -151,6 +152,12 @@ class LayerPlan : public NoCopy
bool first_travel_destination_is_inside_; //!< Whether the destination of the first planned travel move is inside a layer part
std::optional<std::pair<Acceleration, Velocity>> first_extrusion_acc_jerk_; //!< The acceleration and jerk rates of the first extruded move (if this layer is not empty).
std::optional<std::pair<Acceleration, Velocity>> next_layer_acc_jerk_; //!< If there is a next layer, the first acceleration and jerk it starts with.
struct NextLayerHopReturn
{
Velocity fastest_speed;
coord_t z;
};
std::optional<NextLayerHopReturn> next_layer_hop_return_; //!< Conservative timing for a cool-lift hop that ends on the next layer.
bool was_inside_; //!< Whether the last planned (extrusion) move was inside a layer part
bool is_inside_; //!< Whether the destination of the next planned travel move is inside a layer part
Shape comb_boundary_minimum_; //!< The minimum boundary within which to comb, or to move into when performing a retraction.
Expand Down
1 change: 1 addition & 0 deletions include/gcode_export/gcodeExport.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class GCodeExport : public NoCopy
friend class GCodeExportTest;
friend class GriffinHeaderTest;
friend class AntiOozeAmountsTest;
friend class LayerPlanTest;
FRIEND_TEST(GCodeExportTest, CommentEmpty);
FRIEND_TEST(GCodeExportTest, CommentSimple);
FRIEND_TEST(GCodeExportTest, CommentMultiLine);
Expand Down
32 changes: 30 additions & 2 deletions src/LayerPlan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "plugins/slots.h"
#include "raft.h" // getTotalExtraLayers
#include "range/v3/view/chunk_by.hpp"
#include "settings/types/Duration.h"
#include "settings/types/Ratio.h"
#include "sliceDataStorage.h"
#include "utils/Simplify.h"
Expand Down Expand Up @@ -4047,12 +4048,39 @@ void LayerPlan::writeGCode(GCodeExport& gcode)
const RetractionAndWipeConfig& actual_retraction_config
= current_mesh ? current_mesh->retraction_wipe_config : storage_.retraction_wipe_config_per_extruder[gcode.getExtruderNr()];
gcode.writeRetraction(actual_retraction_config.retraction_config);
Duration cool_lift_travel_time{ 0.0 };
if (extruder_plan_idx == extruder_plans_.size() - 1 || ! extruder.settings_.get<bool>("machine_extruder_end_pos_abs"))
{ // only do the z-hop if it's the last extruder plan; otherwise it's already at the switching bay area
// or do it anyway when we switch extruder in-place
gcode.writeZhopStart(MM2INT(3.0));
constexpr coord_t hop_height = MM2INT(3.0);
gcode.writeZhopStart(hop_height);
const Velocity hop_speed = extruder.settings_.get<Velocity>("speed_z_hop");
if (hop_speed > 0.0)
{
Velocity fastest_return_speed = hop_speed;
const auto& extruders = Application::getInstance().current_slice_->scene.extruders;
for (size_t next_idx = extruder_plan_idx + 1; next_idx < extruder_plans_.size(); ++next_idx)
{
fastest_return_speed = std::max(fastest_return_speed, extruders[extruder_plans_[next_idx].extruder_nr_].settings_.get<Velocity>("speed_z_hop"));
}

coord_t return_distance = hop_height;
if (next_layer_hop_return_)
{
fastest_return_speed = std::max(fastest_return_speed, next_layer_hop_return_->fastest_speed);
return_distance = std::max<coord_t>(0, hop_height - std::max<coord_t>(0, next_layer_hop_return_->z - z_));
}

// A later plan may end the hop after switching extruders. Credit only the
// return time guaranteed by its distance and the fastest possible hop speed.
cool_lift_travel_time = Duration(INT2MM(hop_height) / static_cast<double>(hop_speed) + INT2MM(return_distance) / static_cast<double>(fastest_return_speed));
}
}
const Duration remaining_dwell = Duration(extruder_plan.extra_time_) - cool_lift_travel_time;
if (remaining_dwell > 0.0)
{
gcode.writeDelay(remaining_dwell);
}
gcode.writeDelay(extruder_plan.extra_time_);
}

extruder_plan.handleAllRemainingInserts(gcode);
Expand Down
17 changes: 17 additions & 0 deletions src/LayerPlanBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "LayerPlanBuffer.h"

#include <algorithm>

#include <range/v3/algorithm/find_if.hpp>
#include <spdlog/spdlog.h>

Expand Down Expand Up @@ -124,6 +126,21 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer
return;
}

const auto& extruders = Application::getInstance().current_slice_->scene.extruders;
Velocity fastest_hop_speed{ 0.0 };
for (const ExtruderPlan& plan : newest_layer->extruder_plans_)
{
fastest_hop_speed = std::max(fastest_hop_speed, extruders[plan.extruder_nr_].settings_.get<Velocity>("speed_z_hop"));
}
const size_t first_extruder = newest_layer->extruder_plans_.front().extruder_nr_;
const WipeScriptConfig& wipe_config = newest_layer->storage_.retraction_wipe_config_per_extruder[first_extruder].wipe_config;
// A layer-start wipe can replace the pending hop at its own speed.
if (wipe_config.clean_between_layers && wipe_config.hop_enable)
{
fastest_hop_speed = std::max(fastest_hop_speed, wipe_config.hop_speed);
}
prev_layer->next_layer_hop_return_ = LayerPlan::NextLayerHopReturn{ fastest_hop_speed, newest_layer->z_ };

Point2LL first_location_new_layer = new_layer_destination_state->first;

assert(newest_layer->extruder_plans_.front().paths_[0].points.size() == 1);
Expand Down
92 changes: 92 additions & 0 deletions tests/LayerPlanTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
#include "LayerPlan.h" //The code under test.

#include <algorithm>
#include <sstream>

#include <gtest/gtest.h>

#include "Application.h" //To provide settings for the layer plan.
#include "RetractionConfig.h" //To provide retraction settings.
#include "Slice.h" //To provide settings for the layer plan.
#include "arcus/MockCommunication.h" //To prevent calls to any missing Communication class.
#include "gcode_export/GCodePart.h" //To read the written g-code parts.
#include "gcode_export/gcodeExport.h" //To capture the dwell written for a small layer.
#include "pathPlanning/Comb.h" //To create a combing path around the layer plan.
#include "pathPlanning/NozzleTempInsert.h" //To provide nozzle temperature commands.
#include "settings/types/Duration.h"
#include "sliceDataStorage.h" //To provide slice data as input for the planning stage.
#include "utils/Coord_t.h"

Expand Down Expand Up @@ -231,6 +236,52 @@ class LayerPlanTest : public testing::Test
{
delete storage;
}

/*!
* Write one extruder plan whose only cooling action is cool_lift_head.
* \param extra_time Pause still required after slowing down, in seconds.
* \param hop_speed_mm_per_s Commanded Z hop speed.
* \param next_hop_speed_mm_per_s Fastest possible return speed on the next layer, if known.
* \return G-code produced for that plan.
*/
std::string writeCoolLiftHead(const double extra_time, const char* hop_speed_mm_per_s, const double next_hop_speed_mm_per_s = 0.0)
{
settings->add("flow_rate_max_extrusion_offset", "0");
settings->add("flow_rate_extrusion_offset_factor", "0");
settings->add("build_volume_fan_nr", "0");
settings->add("acceleration_enabled", "false");
settings->add("acceleration_travel_enabled", "false");
settings->add("jerk_enabled", "false");
settings->add("jerk_travel_enabled", "false");
settings->add("coasting_enable", "false");
settings->add("retract_at_layer_change", "false");
settings->add("cool_lift_head", "true");
settings->add("machine_extruder_end_pos_abs", "false");
settings->add("speed_z_hop", hop_speed_mm_per_s);

storage->retraction_wipe_config_per_extruder[0].wipe_config.clean_between_layers = false;
storage->retraction_wipe_config_per_extruder[0].retraction_config.distance = 0;
layer_plan.extruder_plans_[0].paths_.clear();
layer_plan.extruder_plans_[0].extra_time_ = extra_time;
if (next_hop_speed_mm_per_s > 0.0)
{
layer_plan.next_layer_hop_return_ = LayerPlan::NextLayerHopReturn{ Velocity(next_hop_speed_mm_per_s), layer_plan.z_ + MM2INT(0.1) };
}

Application::getInstance().communication_ = std::make_shared<MockCommunication>();

GCodeExport gcode;
gcode.setZ(layer_plan.z_);
gcode.writeTravel(gcode.getPositionXY(), settings->get<Velocity>("speed_z_hop"));
layer_plan.writeGCode(gcode);

std::stringstream written;
for (const std::shared_ptr<GCodePart>& part : gcode.gcode_parts_)
{
written << part->str();
}
return written.str();
}
};

// Test all combinations of these settings in parameterised tests.
Expand Down Expand Up @@ -672,6 +723,47 @@ TEST_F(OverhangSpeedTest, SpeedFactorSplitAtOverhangBoundary)
EXPECT_TRUE(found_reduced_speed) << "Segment crossing into overhang should have reduced speed";
}

TEST_F(LayerPlanTest, CoolLiftHeadDwellSubtractsRoundTripHopTime)
{
// 3 mm up and 3 mm down at 10 mm/s is 0.6 s, so a 5 s pause dwells 4.4 s.
const std::string gcode = writeCoolLiftHead(5.0, "10");
const Duration hop_time(2.0 * INT2MM(MM2INT(3.0)) / 10.0);
const int dwell_ms = static_cast<int>((Duration(5.0) - hop_time) * 1000.0);

const size_t layer_z = gcode.find(" Z10\n");
ASSERT_NE(layer_z, std::string::npos) << gcode;
EXPECT_NE(gcode.find("G1 Z13\n", layer_z), std::string::npos) << gcode;
EXPECT_NE(gcode.find("G4 P" + std::to_string(dwell_ms)), std::string::npos) << gcode;
EXPECT_EQ(gcode.find("G4 P5000"), std::string::npos) << gcode;
EXPECT_LT(dwell_ms, 5000);
EXPECT_GT(dwell_ms, 4000);
}

TEST_F(LayerPlanTest, CoolLiftHeadSkipsDwellWhenHopCoversExtraTime)
{
// Round trip at 10 mm/s is 0.6 s, which already covers a 0.4 s pause.
const std::string gcode = writeCoolLiftHead(0.4, "10");

const size_t layer_z = gcode.find(" Z10\n");
ASSERT_NE(layer_z, std::string::npos) << gcode;
EXPECT_NE(gcode.find("G1 Z13\n", layer_z), std::string::npos) << gcode;
EXPECT_EQ(gcode.find("G4 "), std::string::npos) << gcode;
}

TEST_F(LayerPlanTest, CoolLiftHeadAccountsForFasterNextExtruderReturn)
{
// The next layer is 0.1 mm higher and its extruder can return the hop at 100 mm/s.
// Only 0.3 s of ascent and at least 0.029 s of descent can count toward the pause.
const std::string gcode = writeCoolLiftHead(0.5, "10", 100.0);
const size_t dwell_pos = gcode.find("G4 P");

const size_t layer_z = gcode.find(" Z10\n");
ASSERT_NE(layer_z, std::string::npos) << gcode;
EXPECT_NE(gcode.find("G1 Z13\n", layer_z), std::string::npos) << gcode;
ASSERT_NE(dwell_pos, std::string::npos) << gcode;
EXPECT_NEAR(std::stoi(gcode.substr(dwell_pos + 4)), 171, 1) << gcode;
}

TEST(NozzleTempInsertTest, SortNozzleTempInsterts)
{
std::vector<NozzleTempInsert> nozzle_temp_inserts{
Expand Down
Loading