From 8aeafcb2f4bcd9c2e6308d98b1c29920a01ee06b Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Mon, 6 Nov 2023 10:49:12 +0100 Subject: [PATCH 001/139] Changed when fan speed is added to the Gcode to better handle fan speeds changed in plugin. Added some code to clean up plugin output, and set first_travel_destination if it was changed. --- src/LayerPlan.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 52305a9521..80a2ac9cb0 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1920,7 +1920,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } } } - gcode.writeFanCommand(extruder_plan.getFanSpeed()); + // Fan speed may already be set by plugin. Prevents two fan speed commands without move in between. + if(!extruder_plan.paths.empty() && extruder_plan.paths.front().fan_speed == -1) + { + gcode.writeFanCommand(extruder_plan.getFanSpeed()); + } std::vector& paths = extruder_plan.paths; extruder_plan.inserts.sort(); @@ -1943,6 +1947,14 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& path = paths[path_idx]; + // Fans need time to reach the new setting. Adjust fan speed as early as possible. If travel paths have a non default fan speed for some reason set it as fan speed. + // As such modification could be made by a plugin. + if(!path.isTravelPath() || path.fan_speed < 0) + { + const double path_fan_speed = path.getFanSpeed(); + gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); + } + if (path.perform_prime) { gcode.writePrimeTrain(extruder.settings.get("speed_travel")); @@ -2095,9 +2107,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool spiralize = path.spiralize; if (! spiralize) // normal (extrusion) move (with coasting) { - // if path provides a valid (in range 0-100) fan speed, use it - const double path_fan_speed = path.getFanSpeed(); - gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); bool coasting = extruder.settings.get("coasting_enable"); if (coasting) @@ -2373,6 +2382,7 @@ bool LayerPlan::writePathWithCoasting( void LayerPlan::applyModifyPlugin() { + bool handled_initial_travel = false; for (auto& extruder_plan : extruder_plans) { scripta::log( @@ -2395,6 +2405,40 @@ void LayerPlan::applyModifyPlugin() extruder_plan.paths = slots::instance().modify(extruder_plan.paths, extruder_plan.extruder_nr, layer_nr); + // Check if the plugin changed first_travel_destination and update it accordingly if it has + if (! handled_initial_travel) + { + for (auto& path : extruder_plan.paths) + { + if (path.isTravelPath() && path.points.size() > 0) + { + if (path.points.front() != first_travel_destination) + { + first_travel_destination = path.points.front(); + first_travel_destination_is_inside = current_mesh->layers[layer_nr].getOutlines().inside(path.points.front()); + } + handled_initial_travel = true; + } + } + } + + size_t removed_count = std::erase_if( + extruder_plan.paths, + [](GCodePath& path) + { + return path.points.empty(); + }); + if (removed_count > 0) + { + spdlog::warn("Removed {} empty paths after plugin slot GCODE_PATHS_MODIFY was executed", removed_count); + } + // Ensure that the output is at least valid enough to not cause crashes. + if (extruder_plan.paths.size() == 0) + { + GCodePath* reinstated_path = getLatestPathWithConfig(configs_storage.travel_config_per_extruder[getExtruder()], SpaceFillType::None); + addTravel_simple(first_travel_destination.value_or(getLastPlannedPositionOrStartingPosition()), reinstated_path); + } + scripta::log( "extruder_plan_1", extruder_plan.paths, @@ -2413,6 +2457,7 @@ void LayerPlan::applyModifyPlugin() scripta::CellVDI{ "is_travel_path", &GCodePath::isTravelPath }, scripta::CellVDI{ "extrusion_mm3_per_mm", &GCodePath::getExtrusionMM3perMM }); } + } void LayerPlan::applyBackPressureCompensation() From 64467ecf196b9681f72e0dd3880e56a8e2a386c4 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:28:06 +0100 Subject: [PATCH 002/139] Fix issues with first_travel_destination detection after plugins were applied --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 80a2ac9cb0..b026482b9b 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2415,9 +2415,9 @@ void LayerPlan::applyModifyPlugin() if (path.points.front() != first_travel_destination) { first_travel_destination = path.points.front(); - first_travel_destination_is_inside = current_mesh->layers[layer_nr].getOutlines().inside(path.points.front()); } handled_initial_travel = true; + break; } } } From 404c35d68715c93a0d5c186b0c1724f54058be4d Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 8 Nov 2023 03:54:03 +0100 Subject: [PATCH 003/139] Fix per object settings not sent to plugin. Added "mesh_name" field so that the objects can be later identified. --- src/plugins/converters.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp index 72a8166de7..31ee0aa394 100644 --- a/src/plugins/converters.cpp +++ b/src/plugins/converters.cpp @@ -49,12 +49,28 @@ broadcast_settings_request::value_type broadcast_settings_request::operator()(co } auto* object_settings = message.mutable_object_settings(); - for (const auto& object : slice_message.object_lists()) + for (const auto& mesh_group : slice_message.object_lists()) { - auto* settings = object_settings->Add()->mutable_settings(); - for (const auto& setting : object.settings()) + std::unordered_map mesh_group_settings; + for (const auto& setting : mesh_group.settings()) { - settings->emplace(setting.name(), setting.value()); + mesh_group_settings[setting.name()] = setting.value(); + } + for (const auto& object : mesh_group.objects()) + { + std::unordered_map per_object_settings = mesh_group_settings; + for (const auto& setting : object.settings()) + { + per_object_settings[setting.name()] = setting.value(); + } + + per_object_settings["mesh_name"] = object.name(); + + auto* settings = object_settings->Add()->mutable_settings(); + for (const auto& key_value_pair : per_object_settings) + { + settings->emplace(key_value_pair.first, key_value_pair.second); + } } } From 3bae85189e69585a055d5dc1fbdac73397b06464 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:27:54 +0100 Subject: [PATCH 004/139] Fix final travel move using default fan speed --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b026482b9b..6fa21391a7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1949,7 +1949,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // Fans need time to reach the new setting. Adjust fan speed as early as possible. If travel paths have a non default fan speed for some reason set it as fan speed. // As such modification could be made by a plugin. - if(!path.isTravelPath() || path.fan_speed < 0) + if(!path.isTravelPath() || path.fan_speed >= 0) { const double path_fan_speed = path.getFanSpeed(); gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); From 0d3439f457c4e2cffcba825894bbd1ce72eade55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20J=C3=A4ger?= Date: Wed, 3 Jul 2024 23:14:24 +0200 Subject: [PATCH 005/139] Add z-height to infill plugin --- include/plugins/converters.h | 2 +- src/infill.cpp | 3 ++- src/plugins/converters.cpp | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/plugins/converters.h b/include/plugins/converters.h index 9af4e7cbae..bee7a9db41 100644 --- a/include/plugins/converters.h +++ b/include/plugins/converters.h @@ -103,7 +103,7 @@ struct postprocess_response : public details::converter { - value_type operator()(const native_value_type& inner_contour, const std::string& pattern, const Settings& settings) const; + value_type operator()(const native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) const; }; struct infill_generate_response diff --git a/src/infill.cpp b/src/infill.cpp index c085307622..1b19508a2e 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -321,7 +321,8 @@ void Infill::_generate( auto [toolpaths_, generated_result_polygons_, generated_result_lines_] = slots::instance().generate( inner_contour_, mesh ? mesh->settings.get("infill_pattern") : settings.get("infill_pattern"), - mesh ? mesh->settings : settings); + mesh ? mesh->settings : settings, + z_); toolpaths.insert(toolpaths.end(), toolpaths_.begin(), toolpaths_.end()); result_polygons.add(generated_result_polygons_); result_lines.add(generated_result_lines_); diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp index fe6cbe9c0b..fadfabf475 100644 --- a/src/plugins/converters.cpp +++ b/src/plugins/converters.cpp @@ -167,7 +167,7 @@ postprocess_response::native_value_type } infill_generate_request::value_type - infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings) const + infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) const { value_type message{}; message.set_pattern(pattern); @@ -177,6 +177,11 @@ infill_generate_request::value_type msg_settings->insert({ key, value }); } + // ------------------------------------------------------------ + // Add current z height to settings message + // ------------------------------------------------------------ + msg_settings->insert({ "z" , std::to_string(z) }); + if (inner_contour.empty()) { return message; From 66adc991a021b7f8f3c50835a0b2ef032c41eeeb Mon Sep 17 00:00:00 2001 From: "Justin F. Hallett" Date: Thu, 4 Jul 2024 15:12:20 -0600 Subject: [PATCH 006/139] Add 3 new gcode settings - Add bool to set if the start_gcode needs to be the first gcode run. Requires `machine_start_gcode_first` be added to the machine UI as a checkbox - Add a prestart_gcode to extruders, this will allow use set temperatures before the change call so we can preheat the next extruder. Requires `machine_extruder_prestart_code` text box be added to the extruder UI page. - Add a time duration to calculate time required to change extruders. Requires `machine_extruder_change_duration` float box to be added to the exturder UI page. - Fix a bug in the export code that was checking num_extruders instead of used_extruders, this was adding a tool change on start to T0 when only one extruder is used which we do not want, with one extruder it should output like normal and let the user choose the extruder they want to use. --- src/gcodeExport.cpp | 46 +++++++++++++++++++++++++++++++++------- src/sliceDataStorage.cpp | 6 ++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 43fdc8ba3b..b508bc3e33 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -682,6 +682,11 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons writeComment("Generated with Cura_SteamEngine " CURA_ENGINE_VERSION); + if (mesh_group_settings.get("machine_start_gcode_first")) + { + writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); + } + if (getFlavor() == EGCodeFlavor::GRIFFIN) { std::ostringstream tmp; @@ -693,8 +698,11 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons processInitialLayerTemperature(storage, start_extruder_nr); } + if (!mesh_group_settings.get("machine_start_gcode_first")) + { + writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); + } writeExtrusionMode(false); // ensure absolute extrusion mode is set before the start gcode - writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); // in case of shared nozzle assume that the machine-start gcode reset the extruders as per machine description if (Application::getInstance().current_slice_->scene.settings.get("machine_extruders_share_nozzle")) @@ -767,6 +775,8 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage const bool material_print_temp_prepend = scene.current_mesh_group->settings.get("material_print_temp_prepend"); const bool material_print_temp_wait = scene.current_mesh_group->settings.get("material_print_temp_wait"); bool wait_start_extruder = false; + std::vector extruders_used = storage.getExtrudersUsed(); + size_t used_extruders = std::count(extruders_used.begin(), extruders_used.end(), true); switch (getFlavor()) { @@ -776,7 +786,7 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage wait_start_extruder = true; break; default: - if (num_extruders > 1 || getFlavor() == EGCodeFlavor::REPRAP) + if (used_extruders > 1 || getFlavor() == EGCodeFlavor::REPRAP) { std::ostringstream tmp; tmp << "T" << start_extruder_nr; @@ -794,7 +804,6 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage }; std::vector all_extruders; - std::vector extruders_used = storage.getExtrudersUsed(); for (size_t extruder_nr = 0; extruder_nr < extruders_used.size(); ++extruder_nr) { if (extruders_used[extruder_nr]) @@ -1267,6 +1276,29 @@ void GCodeExport::writeZhopEnd(Velocity speed /*= 0*/) void GCodeExport::startExtruder(const size_t new_extruder) { + const auto extruder_settings = Application::getInstance().current_slice_->scene.extruders[new_extruder].settings_; + const auto prestart_code = extruder_settings.get("machine_extruder_prestart_code"); + const auto start_code = extruder_settings.get("machine_extruder_start_code"); + const auto start_code_duration = extruder_settings.get("machine_extruder_start_code_duration"); + const auto extruder_change_duration = extruder_settings.get("machine_extruder_change_duration"); + + // Be nice to be able to calculate the extruder change time verses time + // to heat and run this so it's run before the change call. **Future note** + if (! prestart_code.empty()) + { + if (relative_extrusion_) + { + writeExtrusionMode(false); // ensure absolute extrusion mode is set before the prestart gcode + } + + writeCode(prestart_code.c_str()); + + if (relative_extrusion_) + { + writeExtrusionMode(true); // restore relative extrusion mode + } + } + extruder_attr_[new_extruder].is_used_ = true; if (new_extruder != current_extruder_) // wouldn't be the case on the very first extruder start if it's extruder 0 { @@ -1278,15 +1310,16 @@ void GCodeExport::startExtruder(const size_t new_extruder) { *output_stream_ << "T" << new_extruder << new_line_; } + // Only add time is we are actually changing extruders + estimate_calculator_.addTime(extruder_change_duration); } + estimate_calculator_.addTime(start_code_duration); current_extruder_ = new_extruder; assert(getCurrentExtrudedVolume() == 0.0 && "Just after an extruder switch we haven't extruded anything yet!"); resetExtrusionValue(); // zero the E value on the new extruder, just to be sure - const auto extruder_settings = Application::getInstance().current_slice_->scene.extruders[new_extruder].settings_; - const auto start_code = extruder_settings.get("machine_extruder_start_code"); if (! start_code.empty()) { if (relative_extrusion_) @@ -1302,9 +1335,6 @@ void GCodeExport::startExtruder(const size_t new_extruder) } } - const auto start_code_duration = extruder_settings.get("machine_extruder_start_code_duration"); - estimate_calculator_.addTime(start_code_duration); - Application::getInstance().communication_->setExtruderForSend(Application::getInstance().current_slice_->scene.extruders[new_extruder]); Application::getInstance().communication_->sendCurrentPosition(getPositionXY()); diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 697721ccd0..729b9845b9 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -398,6 +398,12 @@ std::vector SliceDataStorage::getExtrudersUsed() const std::vector ret; ret.resize(Application::getInstance().current_slice_->scene.extruders.size(), false); + // set all the false to start, we set them to true if used + for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice_->scene.extruders.size(); extruder_nr++) + { + ret[extruder_nr] = false; + } + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; const EPlatformAdhesion adhesion_type = mesh_group_settings.get("adhesion_type"); if (adhesion_type == EPlatformAdhesion::SKIRT || adhesion_type == EPlatformAdhesion::BRIM) From 2960fd6f2a0c31469967266372c2d6ed70f0c914 Mon Sep 17 00:00:00 2001 From: "Justin F. Hallett" Date: Sat, 10 Aug 2024 15:16:14 -0600 Subject: [PATCH 007/139] Fix merge issue --- src/gcodeExport.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 9263c25e73..4eec8d6a5b 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -787,6 +787,7 @@ void GCodeExport::processInitialLayerExtrudersTemperatures(const SliceDataStorag }; std::vector all_extruders; + std::vector extruders_used = storage.getExtrudersUsed(); for (size_t extruder_nr = 0; extruder_nr < extruders_used.size(); ++extruder_nr) { if (extruders_used[extruder_nr]) From 8b91a8844ffcc5aa5b3cdb2c9b17d1294c299003 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:01:41 +0200 Subject: [PATCH 008/139] Fixed issues caused by merge and improved comments --- src/LayerPlan.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 29a568838c..48b0d926e7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2088,8 +2088,8 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } } } - // Fan speed may already be set by plugin. Prevents two fan speed commands without move in between. - if(!extruder_plan.paths.empty() && extruder_plan.paths.front().fan_speed == -1) + // Fan speed may already be set by a plugin. Prevents two fan speed commands without move in between. + if(!extruder_plan.paths_.empty() && extruder_plan.paths_.front().fan_speed == -1) { gcode.writePrepareFansForExtrusion(extruder_plan.getFanSpeed()); } @@ -2115,8 +2115,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& path = paths[path_idx]; - // Fans need time to reach the new setting. Adjust fan speed as early as possible. If travel paths have a non default fan speed for some reason set it as fan speed. - // As such modification could be made by a plugin. + // If travel paths have a non default fan speed for some reason set it as fan speed as such modification could be made by a plugin. if(!path.isTravelPath() || path.fan_speed >= 0) { const double path_fan_speed = path.getFanSpeed(); @@ -2603,13 +2602,13 @@ void LayerPlan::applyModifyPlugin() // Check if the plugin changed first_travel_destination and update it accordingly if it has if (! handled_initial_travel) { - for (auto& path : extruder_plan.paths) + for (auto& path : extruder_plan.paths_) { if (path.isTravelPath() && path.points.size() > 0) { - if (path.points.front() != first_travel_destination) + if (path.points.front() != first_travel_destination_) { - first_travel_destination = path.points.front(); + first_travel_destination_ = path.points.front(); } handled_initial_travel = true; break; @@ -2618,7 +2617,7 @@ void LayerPlan::applyModifyPlugin() } size_t removed_count = std::erase_if( - extruder_plan.paths, + extruder_plan.paths_, [](GCodePath& path) { return path.points.empty(); @@ -2628,10 +2627,10 @@ void LayerPlan::applyModifyPlugin() spdlog::warn("Removed {} empty paths after plugin slot GCODE_PATHS_MODIFY was executed", removed_count); } // Ensure that the output is at least valid enough to not cause crashes. - if (extruder_plan.paths.size() == 0) + if (extruder_plan.paths_.size() == 0) { - GCodePath* reinstated_path = getLatestPathWithConfig(configs_storage.travel_config_per_extruder[getExtruder()], SpaceFillType::None); - addTravel_simple(first_travel_destination.value_or(getLastPlannedPositionOrStartingPosition()), reinstated_path); + GCodePath* reinstated_path = getLatestPathWithConfig(configs_storage_.travel_config_per_extruder[getExtruder()], SpaceFillType::None); + addTravel_simple(first_travel_destination_.value_or(getLastPlannedPositionOrStartingPosition()), reinstated_path); } scripta::log( @@ -2652,7 +2651,6 @@ void LayerPlan::applyModifyPlugin() scripta::CellVDI{ "is_travel_path", &GCodePath::isTravelPath }, scripta::CellVDI{ "extrusion_mm3_per_mm", &GCodePath::getExtrusionMM3perMM }); } - } void LayerPlan::applyBackPressureCompensation() From 8d2bd2baae13171609604319d48d339edfac2645 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 20 Aug 2024 09:29:05 +0200 Subject: [PATCH 009/139] Make GCodePath use 3D points so that they can have individual Z offsets CURA-12081 --- CMakeLists.txt | 3 +- include/ExtruderPlan.h | 2 +- include/GCodePathConfig.h | 2 +- include/LayerPlan.h | 11 +++ include/geometry/Point2LL.h | 45 +++------ include/geometry/Point3LL.h | 13 ++- include/gradual_flow/FlowLimitedPath.h | 13 ++- include/gradual_flow/Processor.h | 28 +++++- include/infill/ImageBasedDensityProvider.h | 1 + include/pathPlanning/GCodePath.h | 4 +- include/utils/AABB3D.h | 1 + include/utils/Point3D.h | 2 +- include/utils/VoxelUtils.h | 2 +- src/LayerPlan.cpp | 104 ++++++++++++++------- src/LayerPlanBuffer.cpp | 2 +- src/geometry/Point2LL.cpp | 45 +++++++++ src/{utils => geometry}/Point3LL.cpp | 21 +++++ src/plugins/converters.cpp | 7 +- src/utils/ToolpathVisualizer.cpp | 2 + 19 files changed, 222 insertions(+), 86 deletions(-) create mode 100644 src/geometry/Point2LL.cpp rename src/{utils => geometry}/Point3LL.cpp (76%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2287e8f92d..5858a1ab0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,6 @@ set(engine_SRCS # Except main.cpp. src/utils/ListPolyIt.cpp src/utils/Matrix4x3D.cpp src/utils/MinimumSpanningTree.cpp - src/utils/Point3LL.cpp src/utils/PolygonConnector.cpp src/utils/PolygonsPointIndex.cpp src/utils/PolygonsSegmentIndex.cpp @@ -156,6 +155,8 @@ set(engine_SRCS # Except main.cpp. src/utils/VoxelUtils.cpp src/utils/MixedPolylineStitcher.cpp + src/geometry/Point2LL.cpp + src/geometry/Point3LL.cpp src/geometry/Polygon.cpp src/geometry/Shape.cpp src/geometry/PointsSet.cpp diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 338dfba115..d82fb34b1a 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -190,7 +190,7 @@ class ExtruderPlan /*! * @return distance between p0 and p1 as well as the time spend on the segment */ - std::pair getPointToPointTime(const Point2LL& p0, const Point2LL& p1, const GCodePath& path); + std::pair getPointToPointTime(const Point3LL &p0, const Point3LL &p1, const GCodePath& path); /*! * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. diff --git a/include/GCodePathConfig.h b/include/GCodePathConfig.h index 82a2ea78dd..27672049df 100644 --- a/include/GCodePathConfig.h +++ b/include/GCodePathConfig.h @@ -20,7 +20,7 @@ struct GCodePathConfig { static constexpr double FAN_SPEED_DEFAULT = -1.0; - coord_t z_offset{}; // +#include #include #include -#include "geometry/Point3LL.h" +#include "utils/Coord_t.h" #include "utils/types/generic.h" #ifdef __GNUC__ @@ -30,6 +31,8 @@ Integer points are used to avoid floating point rounding errors, and because Cli namespace cura { +class Point3LL; + /* 64bit Points are used mostly throughout the code, these are the 2D points from ClipperLib */ using Point2LL = ClipperLib::IntPoint; @@ -207,39 +210,17 @@ INLINE const Point2LL& make_point(const Point2LL& p) return p; } -inline Point3LL operator+(const Point3LL& p3, const Point2LL& p2) -{ - return { p3.x_ + p2.X, p3.y_ + p2.Y, p3.z_ }; -} +Point2LL operator+(const Point2LL& p2, const Point3LL& p3); -inline Point3LL& operator+=(Point3LL& p3, const Point2LL& p2) -{ - p3.x_ += p2.X; - p3.y_ += p2.Y; - return p3; -} +Point3LL operator+(const Point3LL& p3, const Point2LL& p2); -inline Point2LL operator+(const Point2LL& p2, const Point3LL& p3) -{ - return { p3.x_ + p2.X, p3.y_ + p2.Y }; -} +Point3LL& operator+=(Point3LL& p3, const Point2LL& p2); -inline Point3LL operator-(const Point3LL& p3, const Point2LL& p2) -{ - return { p3.x_ - p2.X, p3.y_ - p2.Y, p3.z_ }; -} +Point3LL operator-(const Point3LL& p3, const Point2LL& p2); -inline Point3LL& operator-=(Point3LL& p3, const Point2LL& p2) -{ - p3.x_ -= p2.X; - p3.y_ -= p2.Y; - return p3; -} +Point3LL& operator-=(Point3LL& p3, const Point2LL& p2); -inline Point2LL operator-(const Point2LL& p2, const Point3LL& p3) -{ - return { p2.X - p3.x_, p2.Y - p3.y_ }; -} +inline Point2LL operator-(const Point2LL& p2, const Point3LL& p3); } // namespace cura @@ -259,4 +240,4 @@ struct hash }; } // namespace std -#endif // UTILS_INT_POINT_H +#endif // GEOMETRY_POINT2LL_H diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index fd220b05ef..1fe449205b 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -10,6 +10,7 @@ #include //For numeric_limits::min and max. #include // for operations on any arithmetic number type +#include "geometry/Point2LL.h" #include "utils/Coord_t.h" #include "utils/types/generic.h" @@ -20,9 +21,9 @@ namespace cura class Point3LL { public: - coord_t x_{}; - coord_t y_{}; - coord_t z_{}; + coord_t x_{ 0 }; + coord_t y_{ 0 }; + coord_t z_{ 0 }; Point3LL() = default; @@ -35,6 +36,8 @@ class Point3LL Point3LL(Point3LL&& point) = default; Point3LL(const Point3LL& point) = default; + Point3LL(const Point2LL& point); + Point3LL& operator=(const Point3LL& point) = default; Point3LL& operator=(Point3LL&& point) = default; @@ -148,6 +151,10 @@ class Point3LL return x_ * p.x_ + y_ * p.y_ + z_ * p.z_; } + [[nodiscard]] Point2LL toPoint2LL() const; + + [[nodiscard]] Point3LL resized(coord_t length) const; + coord_t& operator[](const size_t index) { assert(index < 3); diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index 0e3eab859d..7a8219e678 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -30,7 +30,7 @@ enum class FlowState struct FlowLimitedPath { const GCodePath* original_gcode_path_data; - PointsSet points; + PointsSet points{}; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -135,11 +135,14 @@ struct FlowLimitedPath double totalLength() const // um { double path_length = 0; - auto last_point = points.front(); - for (const auto& point : points | ranges::views::drop(1)) + if (! points.empty()) { - path_length += std::hypot(point.X - last_point.X, point.Y - last_point.Y); - last_point = point; + auto last_point = points.front(); + for (const auto& point : points | ranges::views::drop(1)) + { + path_length += std::hypot(point.X - last_point.X, point.Y - last_point.Y); + last_point = point; + } } return path_length; } diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index c9615d6ba2..48998a6d51 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -32,7 +32,19 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ // Process first path for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = PointsSet(path.points) }); + PointsSet points; + + points.reserve(path.points.size()); + std::transform( + path.points.begin(), + path.points.end(), + std::back_inserter(points), + [](const Point3LL& point) + { + return point.toPoint2LL(); + }); + + gcode_paths.push_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); } /* Process remaining paths @@ -47,8 +59,18 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { PointsSet points{ gcode_paths.back().points.back() }; - points.push_back(PointsSet(path.points)); - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); + + points.reserve(path.points.size() + 1); + std::transform( + path.points.begin(), + path.points.end(), + std::back_inserter(points), + [](const Point3LL& point) + { + return point.toPoint2LL(); + }); + + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = std::move(points) }); } constexpr auto non_zero_flow_view = ranges::views::transform( diff --git a/include/infill/ImageBasedDensityProvider.h b/include/infill/ImageBasedDensityProvider.h index f8b215ecd7..c090ebf729 100644 --- a/include/infill/ImageBasedDensityProvider.h +++ b/include/infill/ImageBasedDensityProvider.h @@ -7,6 +7,7 @@ #include "../utils/AABB.h" #include "DensityProvider.h" +#include "geometry/Point3LL.h" namespace cura { diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 577cd8efcc..9780d1bda8 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -29,7 +29,7 @@ namespace cura */ struct GCodePath { - coord_t z_offset{}; // mesh; //!< Which mesh this path belongs to, if any. If it's not part of any mesh, the mesh should be nullptr; SpaceFillType space_fill_type{}; //!< The type of space filling of which this path is a part @@ -46,7 +46,7 @@ struct GCodePath bool perform_z_hop{ false }; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path. bool perform_prime{ false }; //!< Whether this path is preceded by a prime (blob) bool skip_agressive_merge_hint{ false }; //!< Wheter this path needs to skip merging if any travel paths are in between the extrusions. - std::vector points{}; //!< The points constituting this path. + std::vector points{}; //!< The points constituting this path. The Z coordinate is an offset relative to the actual layer height, added to the global z_offset. bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise TimeMaterialEstimates estimates{}; //!< Naive time and material estimates diff --git a/include/utils/AABB3D.h b/include/utils/AABB3D.h index 402135be66..e7d4b9eda5 100644 --- a/include/utils/AABB3D.h +++ b/include/utils/AABB3D.h @@ -5,6 +5,7 @@ #define UTILS_AABB3D_H #include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" #include "utils/AABB.h" namespace cura diff --git a/include/utils/Point3D.h b/include/utils/Point3D.h index 6000d9be76..fe3b7765f8 100644 --- a/include/utils/Point3D.h +++ b/include/utils/Point3D.h @@ -7,7 +7,7 @@ #include #include -#include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" namespace cura diff --git a/include/utils/VoxelUtils.h b/include/utils/VoxelUtils.h index 9eaca6e447..050d03ebb6 100644 --- a/include/utils/VoxelUtils.h +++ b/include/utils/VoxelUtils.h @@ -7,7 +7,7 @@ #include #include -#include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" #include "geometry/Polygon.h" namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index ce654d76ec..ee9f632c27 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -448,7 +448,7 @@ GCodePath& LayerPlan::addTravel(const Point2LL& p, const bool force_retract, con } for (Point2LL& comb_point : combPath) { - if (path->points.empty() || vSize2(path->points.back() - comb_point) > maximum_travel_resolution * maximum_travel_resolution) + if (path->points.empty() || (path->points.back() - comb_point).vSize2() > maximum_travel_resolution * maximum_travel_resolution) { path->points.push_back(comb_point); distance += vSize(last_point - comb_point); @@ -1497,6 +1497,23 @@ void LayerPlan::addLinesInGivenOrder( } } +void LayerPlan::writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset) +{ + gcode.writeTravel(position + Point3LL(0, 0, z_ + path_z_offset), speed); +} + +void LayerPlan::writeExtrusionRelativeZ( + GCodeExport& gcode, + const Point3LL& position, + const Velocity& speed, + const coord_t path_z_offset, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + bool update_extrusion_offset) +{ + gcode.writeExtrusion(position + Point3LL(0, 0, z_ + path_z_offset), speed, extrusion_mm3_per_mm, feature, update_extrusion_offset); +} + void LayerPlan::addLinesMonotonic( const Shape& area, const OpenLinesSet& lines, @@ -1799,15 +1816,15 @@ double ExtruderPlan::getRetractTime(const GCodePath& path) return retraction_config_.distance / (path.retract ? retraction_config_.speed : retraction_config_.primeSpeed); } -std::pair ExtruderPlan::getPointToPointTime(const Point2LL& p0, const Point2LL& p1, const GCodePath& path) +std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) { - const double length = vSizeMM(p0 - p1); + const double length = (p0 - p1).vSizeMM(); return { length, length / (path.config.getSpeed() * path.speed_factor) }; } TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point2LL starting_position) { - Point2LL p0 = starting_position; + Point3LL p0 = starting_position; const double min_path_speed = fan_speed_layer_time_settings_.cool_min_speed; slowest_path_speed_ = std::accumulate( @@ -1859,9 +1876,9 @@ TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point2LL starting_ path.estimates.unretracted_travel_time += 0.5 * retract_unretract_time; } } - for (Point2LL& p1 : path.points) + for (Point3LL& p1 : path.points) { - double length = vSizeMM(p0 - p1); + double length = (p0 - p1).vSizeMM(); if (is_extrusion_path) { if (length > 0) @@ -1968,7 +1985,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) if (! extruder_plan.paths_.empty() && ! extruder_plan.paths_.back().points.empty()) { - starting_position = extruder_plan.paths_.back().points.back(); + starting_position = extruder_plan.paths_.back().points.back().toPoint2LL(); } } } @@ -2119,6 +2136,8 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& path = paths[path_idx]; + assert(! path.points.empty()); + if (path.perform_prime) { gcode.writePrimeTrain(extruder.settings_.get("speed_travel")); @@ -2218,6 +2237,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) else { gcode.writeZhopEnd(); + if (z_ > 0 && path.z_offset != 0) { gcode.setZ(z_ + path.z_offset); @@ -2254,10 +2274,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeComment(ss.str()); } - if (! path.spiralize && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset != gcode.getPositionZ()) && (path_idx > 0 || layer_nr_ > 0)) + if (! path.spiralize && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset + path.points.front().z_ != gcode.getPositionZ()) + && (path_idx > 0 || layer_nr_ > 0)) { // First move to desired height to then make a plain horizontal move - gcode.writeTravel(Point3LL(gcode.getPosition().x_, gcode.getPosition().y_, z_ + path.z_offset), speed); + gcode.writeTravel(Point3LL(gcode.getPosition().x_, gcode.getPosition().y_, z_ + path.z_offset + path.points.front().z_), speed); } if (path.config.isTravelPath()) @@ -2274,7 +2295,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } for (size_t point_idx = 0; point_idx + 1 < path.points.size(); point_idx++) { - gcode.writeTravel(path.points[point_idx], speed); + writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); } if (path.unretract_before_last_travel_move && final_travel_z_ == z_) { @@ -2283,7 +2304,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } if (! path.points.empty()) { - gcode.writeTravel(path.points.back(), speed); + writeTravelRelativeZ(gcode, path.points.back(), speed, path.z_offset); } continue; } @@ -2302,15 +2323,23 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm - Point2LL prev_point = gcode.getPositionXY(); + Point3LL prev_point = gcode.getPosition(); for (unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++) { const auto [_, time] = extruder_plan.getPointToPointTime(prev_point, path.points[point_idx], path); insertTempOnTime(time, path_idx); const double extrude_speed = speed * path.speed_back_pressure_factor; - communication->sendLineTo(path.config.type, path.points[point_idx], path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); - gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config.type, update_extrusion_offset); + communication + ->sendLineTo(path.config.type, path.points[point_idx].toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + writeExtrusionRelativeZ( + gcode, + path.points[point_idx], + extrude_speed, + path.z_offset, + path.getExtrusionMM3perMM(), + path.config.type, + update_extrusion_offset); prev_point = path.points[point_idx]; } @@ -2326,7 +2355,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& _path = paths[_path_idx]; for (unsigned int point_idx = 0; point_idx < _path.points.size(); point_idx++) { - Point2LL p1 = _path.points[point_idx]; + Point2LL p1 = _path.points[point_idx].toPoint2LL(); totalLength += vSizeMM(p0 - p1); p0 = p1; } @@ -2340,7 +2369,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (unsigned int point_idx = 0; point_idx < spiral_path.points.size(); point_idx++) { - const Point2LL p1 = spiral_path.points[point_idx]; + const Point2LL p1 = spiral_path.points[point_idx].toPoint2LL(); length += vSizeMM(p0 - p1); p0 = p1; gcode.setZ(std::round(z_ + layer_thickness_ * length / totalLength)); @@ -2348,11 +2377,18 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const double extrude_speed = speed * spiral_path.speed_back_pressure_factor; communication->sendLineTo( spiral_path.config.type, - spiral_path.points[point_idx], + spiral_path.points[point_idx].toPoint2LL(), spiral_path.getLineWidthForLayerView(), spiral_path.config.getLayerThickness(), extrude_speed); - gcode.writeExtrusion(spiral_path.points[point_idx], extrude_speed, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, update_extrusion_offset); + writeExtrusionRelativeZ( + gcode, + spiral_path.points[point_idx], + extrude_speed, + path.z_offset, + spiral_path.getExtrusionMM3perMM(), + spiral_path.config.type, + update_extrusion_offset); } // for layer display only - the loop finished at the seam vertex but as we started from // the location of the previous layer's seam vertex the loop may have a gap if this layer's @@ -2363,8 +2399,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // vertex would not be shifted (as it's the last vertex in the sequence). The smoother the model, // the less the vertices are shifted and the less obvious is the ridge. If the layer display // really displayed a spiral rather than slices of a spiral, this would not be required. - communication - ->sendLineTo(spiral_path.config.type, spiral_path.points[0], spiral_path.getLineWidthForLayerView(), spiral_path.config.getLayerThickness(), speed); + communication->sendLineTo( + spiral_path.config.type, + spiral_path.points[0].toPoint2LL(), + spiral_path.getLineWidthForLayerView(), + spiral_path.config.getLayerThickness(), + speed); } path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path } @@ -2485,11 +2525,11 @@ bool LayerPlan::writePathWithCoasting( std::optional acc_dist_idx_gt_coast_dist; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point) // == the point printed BEFORE the start point for coasting - const Point2LL* last = &path.points[path.points.size() - 1]; + const Point3LL* last = &path.points[path.points.size() - 1]; for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++) { - const Point2LL& point = path.points[path.points.size() - 1 - backward_point_idx]; - const coord_t distance = vSize(point - *last); + const Point3LL& point = path.points[path.points.size() - 1 - backward_point_idx]; + const coord_t distance = (point - *last).vSize(); accumulated_dist += distance; accumulated_dist_per_point.push_back(accumulated_dist); @@ -2534,15 +2574,15 @@ bool LayerPlan::writePathWithCoasting( const size_t point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist.value(); - Point2LL start; + Point3LL start; { // computation of begin point of coasting const coord_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value() - 1]; - const Point2LL& a = path.points[point_idx_before_start]; - const Point2LL& b = path.points[point_idx_before_start + 1]; - start = b + normal(a - b, residual_dist); + const Point3LL& a = path.points[point_idx_before_start]; + const Point3LL& b = path.points[point_idx_before_start + 1]; + start = b + (a - b).resized(residual_dist); } - Point2LL prev_pt = gcode.getPositionXY(); + Point3LL prev_pt = gcode.getPositionXY(); { // write normal extrude path: Communication* communication = Application::getInstance().communication_; for (size_t point_idx = 0; point_idx <= point_idx_before_start; point_idx++) @@ -2550,12 +2590,12 @@ bool LayerPlan::writePathWithCoasting( auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); insertTempOnTime(time, path_idx); - communication->sendLineTo(path.config.type, path.points[point_idx], path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); - gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + communication->sendLineTo(path.config.type, path.points[point_idx].toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); prev_pt = path.points[point_idx]; } - communication->sendLineTo(path.config.type, start, path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + communication->sendLineTo(path.config.type, start.toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); } @@ -2567,7 +2607,7 @@ bool LayerPlan::writePathWithCoasting( const Ratio coasting_speed_modifier = extruder.settings_.get("coasting_speed"); const Velocity speed = Velocity(coasting_speed_modifier * path.config.getSpeed()); - gcode.writeTravel(path.points[point_idx], speed); + writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); prev_pt = path.points[point_idx]; } diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index db64a130a5..426c077055 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -91,7 +91,7 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer Point2LL first_location_new_layer = new_layer_destination_state->first; assert(newest_layer->extruder_plans_.front().paths_[0].points.size() == 1); - assert(newest_layer->extruder_plans_.front().paths_[0].points[0] == first_location_new_layer); + assert(newest_layer->extruder_plans_.front().paths_[0].points[0].toPoint2LL() == first_location_new_layer); // if the last planned position in the previous layer isn't the same as the first location of the new layer, travel to the new location if (! prev_layer->last_planned_position_ || *prev_layer->last_planned_position_ != first_location_new_layer) diff --git a/src/geometry/Point2LL.cpp b/src/geometry/Point2LL.cpp new file mode 100644 index 0000000000..d3071b82af --- /dev/null +++ b/src/geometry/Point2LL.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "geometry/Point2LL.h" //The headers we're implementing. + +#include "geometry/Point3LL.h" + +namespace cura +{ + +Point2LL operator+(const Point2LL& p2, const Point3LL& p3) +{ + return { p3.x_ + p2.X, p3.y_ + p2.Y }; +} + +Point3LL operator+(const Point3LL& p3, const Point2LL& p2) +{ + return { p3.x_ + p2.X, p3.y_ + p2.Y, p3.z_ }; +} + +Point3LL& operator+=(Point3LL& p3, const Point2LL& p2) +{ + p3.x_ += p2.X; + p3.y_ += p2.Y; + return p3; +} + +Point3LL operator-(const Point3LL& p3, const Point2LL& p2) +{ + return { p3.x_ - p2.X, p3.y_ - p2.Y, p3.z_ }; +} + +Point3LL& operator-=(Point3LL& p3, const Point2LL& p2) +{ + p3.x_ -= p2.X; + p3.y_ -= p2.Y; + return p3; +} + +Point2LL operator-(const Point2LL& p2, const Point3LL& p3) +{ + return { p2.X - p3.x_, p2.Y - p3.y_ }; +} + +} // namespace cura diff --git a/src/utils/Point3LL.cpp b/src/geometry/Point3LL.cpp similarity index 76% rename from src/utils/Point3LL.cpp rename to src/geometry/Point3LL.cpp index bb8e7c6930..3dd6c15ec4 100644 --- a/src/utils/Point3LL.cpp +++ b/src/geometry/Point3LL.cpp @@ -6,6 +6,12 @@ namespace cura { +Point3LL::Point3LL(const Point2LL& point) + : x_(point.X) + , y_(point.Y) +{ +} + Point3LL Point3LL::operator+(const Point3LL& p) const { return Point3LL(x_ + p.x_, y_ + p.y_, z_ + p.z_); @@ -63,4 +69,19 @@ Point3LL& Point3LL::operator/=(const Point3LL& p) return *this; } +Point2LL Point3LL::toPoint2LL() const +{ + return Point2LL(x_, y_); +} + +Point3LL Point3LL::resized(coord_t length) const +{ + const coord_t actual_length = vSize(); + if (actual_length < 1) + { + return { length, 0, 0 }; + } + return ((*this) * length) / actual_length; +} + } // namespace cura diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp index 964a50007d..f38a16fddb 100644 --- a/src/plugins/converters.cpp +++ b/src/plugins/converters.cpp @@ -337,8 +337,9 @@ gcode_paths_modify_request::value_type for (const auto& point : path.points) { auto* points = gcode_path->mutable_path()->add_path(); - points->set_x(point.X); - points->set_y(point.Y); + points->set_x(point.x_); + points->set_y(point.y_); + points->set_z(point.z_); } gcode_path->set_space_fill_type(getSpaceFillType(path.space_fill_type)); gcode_path->set_flow(path.flow); @@ -478,7 +479,7 @@ gcode_paths_modify_response::native_value_type | ranges::views::transform( [](const auto& point_msg) { - return Point2LL{ point_msg.x(), point_msg.y() }; + return Point3LL{ point_msg.x(), point_msg.y(), point_msg.z() }; }) | ranges::to_vector; diff --git a/src/utils/ToolpathVisualizer.cpp b/src/utils/ToolpathVisualizer.cpp index 88161c78c7..84675f992f 100644 --- a/src/utils/ToolpathVisualizer.cpp +++ b/src/utils/ToolpathVisualizer.cpp @@ -4,6 +4,8 @@ #include +#include "geometry/Point3LL.h" + namespace cura { From cf3d3f9ed926213e5de41c967b1cb1a40abbd888 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 20 Aug 2024 10:23:14 +0200 Subject: [PATCH 010/139] Remove unused communication methods CURA-12081 Less code, less bugs --- include/communication/ArcusCommunication.h | 36 ++-------------- include/communication/CommandLine.h | 14 ------- include/communication/Communication.h | 28 ------------- src/communication/ArcusCommunication.cpp | 49 ---------------------- src/communication/CommandLine.cpp | 6 --- 5 files changed, 3 insertions(+), 130 deletions(-) diff --git a/include/communication/ArcusCommunication.h b/include/communication/ArcusCommunication.h index 2638fed7e1..48d653121f 100644 --- a/include/communication/ArcusCommunication.h +++ b/include/communication/ArcusCommunication.h @@ -113,7 +113,7 @@ class ArcusCommunication : public Communication * visualisation of the layer. * * This will be called after all the polygons and lines of this layer are - * sent via sendPolygons, sendPolygon and sendLineTo. This will flush all + * sent via sendLineTo. This will flush all * visualised data for one layer in one go. * \param layer_nr The layer that was completed. * \param z The z-coordinate of the top side of the layer. @@ -142,34 +142,6 @@ class ArcusCommunication : public Communication */ void sendOptimizedLayerData() override; - /* - * \brief Send a polygon to the front-end to display in layer view. - * - * The polygons are not actually flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygon represents (infill, - * wall, support, etc). - * \param polygon The shape to visualise. - * \param line_width The width of the lines in this polygon. - * \param line_thickness The thickness (in the Z direction) of the polygon. - * \param velocity The velocity of printing this polygon. - */ - void sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; - - /* - * \brief Send polygons to the front-end to display in layer view. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygons represent (infill, - * wall, support, etc). - * \param polygons The shapes to visualise. - * \param line_width The width of the lines in these polygons. - * \param line_thickness The thickness (in the Z direction) of the polygons. - * \param velocity The velocity of printing these polygons. - */ - void sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; - /* * \brief Send an estimate of how long the print would take and how much * material it would use. @@ -182,15 +154,13 @@ class ArcusCommunication : public Communication void sendProgress(double progress) const override; /* - * \brief Set which extruder is being used for the following calls to - * ``sendPolygon``, ``sendPolygons`` and ``sendLineTo``. + * \brief Set which extruder is being used for the following calls to ``sendLineTo``. * \param extruder The new extruder to send data for. */ void setExtruderForSend(const ExtruderTrain& extruder) override; /* - * \brief Set which layer is being used for the following calls to - * ``sendPolygon``, ``sendPolygons`` and ``sendLineTo``. + * \brief Set which layer is being used for the following calls to ``sendLineTo``. * \param layer_nr The index of the layer to send data for. This is zero- * indexed but may be negative for raft layers. */ diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 55c742871a..5ee65a325b 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -107,20 +107,6 @@ class CommandLine : public Communication */ void sendOptimizedLayerData() override; - /* - * \brief Send a polygon to show it in layer view. - * - * The command line doesn't show any layer view so this is ignored. - */ - void sendPolygon(const PrintFeatureType&, const Polygon&, const coord_t&, const coord_t&, const Velocity&) override; - - /* - * \brief Send a polygon to show it in layer view. - * - * The command line doesn't show any layer view so this is ignored. - */ - void sendPolygons(const PrintFeatureType&, const Shape&, const coord_t&, const coord_t&, const Velocity&) override; - /* * \brief Show an estimate of how long the print would take and how much * material it would use. diff --git a/include/communication/Communication.h b/include/communication/Communication.h index 9bc990c11e..387bc20ccb 100644 --- a/include/communication/Communication.h +++ b/include/communication/Communication.h @@ -64,34 +64,6 @@ class Communication */ virtual void sendLayerComplete(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness) = 0; - /* - * \brief Send polygons to the user to visualise. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygons represent (infill, - * wall, support, etc). - * \param polygons The shapes to visualise. - * \param line_width The width of the lines in these polygons. - * \param line_thickness The thickness (in the Z direction) of the polygons. - * \param velocity The velocity of printing these polygons. - */ - virtual void sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; - - /* - * \brief Send a polygon to the user to visualise. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygon represents (infill, - * wall, support, etc). - * \param polygon The shape to visualise. - * \param line_width The width of the lines in this polygon. - * \param line_thickness The thickness (in the Z direction) of the polygon. - * \param velocity The velocity of printing this polygon. - */ - virtual void sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; - /* * \brief Send a line to the user to visualise. * diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 0f0fd4e6d3..1a75fe2516 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -227,42 +227,6 @@ class ArcusCommunication::PathCompiler } } - /*! - * \brief Adds closed polygon to the current path. - * \param print_feature_type The type of feature that the polygon is part of - * (infill, wall, etc). - * \param polygon The shape of the polygon. - * \param width The width of the lines of the polygon. - * \param thickness The layer thickness of the polygon. - * \param velocity How fast the polygon is printed. - */ - void sendPolygon(const PrintFeatureType& print_feature_type, const Polygon& polygon, const coord_t& width, const coord_t& thickness, const Velocity& velocity) - { - if (polygon.size() < 2) // Don't send single points or empty polygons. - { - return; - } - - ClipperLib::Path::const_iterator point = polygon.begin(); - handleInitialPoint(*point); - - // Send all coordinates one by one. - while (++point != polygon.end()) - { - if (*point == last_point) - { - continue; // Ignore zero-length segments. - } - addLineSegment(print_feature_type, *point, width, thickness, velocity); - } - - // Make sure the polygon is closed. - if (*polygon.begin() != polygon.back()) - { - addLineSegment(print_feature_type, *polygon.begin(), width, thickness, velocity); - } - } - private: /*! * \brief Convert and add a point to the points buffer. @@ -444,19 +408,6 @@ void ArcusCommunication::sendOptimizedLayerData() data.slice_data.clear(); } -void ArcusCommunication::sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) -{ - path_compiler->sendPolygon(type, polygon, line_width, line_thickness, velocity); -} - -void ArcusCommunication::sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) -{ - for (const Polygon& polygon : polygons) - { - path_compiler->sendPolygon(type, polygon, line_width, line_thickness, velocity); - } -} - void ArcusCommunication::sendPrintTimeMaterialEstimates() const { spdlog::debug("Sending print time and material estimates."); diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 12e04adc13..269978ff7f 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -71,12 +71,6 @@ void CommandLine::sendLineTo(const PrintFeatureType&, const Point2LL&, const coo void CommandLine::sendOptimizedLayerData() { } -void CommandLine::sendPolygon(const PrintFeatureType&, const Polygon&, const coord_t&, const coord_t&, const Velocity&) -{ -} -void CommandLine::sendPolygons(const PrintFeatureType&, const Shape&, const coord_t&, const coord_t&, const Velocity&) -{ -} void CommandLine::setExtruderForSend(const ExtruderTrain&) { } From 636a6afe4de8a825c540bb3861535fdad7341bc2 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 21 Aug 2024 10:17:20 +0200 Subject: [PATCH 011/139] Send 3D points to the front-end so that we can display Z changes CURA-12081 --- include/LayerPlan.h | 2 ++ include/communication/ArcusCommunication.h | 4 +-- include/communication/CommandLine.h | 4 +-- include/communication/Communication.h | 4 +-- src/LayerPlan.cpp | 33 +++++++++++----------- src/communication/ArcusCommunication.cpp | 32 ++++++++++----------- src/communication/CommandLine.cpp | 4 +-- src/gcodeExport.cpp | 10 +++++-- 8 files changed, 49 insertions(+), 44 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 7ebce6d2e7..788aee555d 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -820,6 +820,8 @@ class LayerPlan : public NoCopy const Ratio flow_ratio, const double fan_speed); + void sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed); + void writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset); void writeExtrusionRelativeZ( diff --git a/include/communication/ArcusCommunication.h b/include/communication/ArcusCommunication.h index 48d653121f..f6a8c81f66 100644 --- a/include/communication/ArcusCommunication.h +++ b/include/communication/ArcusCommunication.h @@ -86,7 +86,7 @@ class ArcusCommunication : public Communication * This may indicate the starting position (or any other jump in the path). * \param position The current position to start the next line at. */ - void sendCurrentPosition(const Point2LL& position) override; + void sendCurrentPosition(const Point3LL &position) override; /* * \brief Sends a message to indicate that all the slicing is done. @@ -132,7 +132,7 @@ class ArcusCommunication : public Communication * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - void sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; + void sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; /* * \brief Send the sliced layer data to the front-end after the optimisation diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 5ee65a325b..ed6b1cbbb2 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -64,7 +64,7 @@ class CommandLine : public Communication * The command line doesn't do anything with the current position so this is * ignored. */ - void sendCurrentPosition(const Point2LL&) override; + void sendCurrentPosition(const Point3LL&) override; /* * \brief Indicate to the command line that we finished slicing. @@ -98,7 +98,7 @@ class CommandLine : public Communication * * The command line doesn't show any layer view so this is ignored. */ - void sendLineTo(const PrintFeatureType&, const Point2LL&, const coord_t&, const coord_t&, const Velocity&) override; + void sendLineTo(const PrintFeatureType&, const Point3LL&, const coord_t&, const coord_t&, const Velocity&) override; /* * \brief Complete a layer to show it in layer view. diff --git a/include/communication/Communication.h b/include/communication/Communication.h index 387bc20ccb..1a70b15746 100644 --- a/include/communication/Communication.h +++ b/include/communication/Communication.h @@ -76,7 +76,7 @@ class Communication * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - virtual void sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; + virtual void sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; /* * \brief Send the current position to visualise. @@ -84,7 +84,7 @@ class Communication * This may indicate the starting position (or any other jump in the path). * \param position The current position to start the next line at. */ - virtual void sendCurrentPosition(const Point2LL& position) = 0; + virtual void sendCurrentPosition(const Point3LL& position) = 0; /* * \brief Set which extruder is being used for the following calls to diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index ee9f632c27..9ef5d1276e 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1497,6 +1497,17 @@ void LayerPlan::addLinesInGivenOrder( } } +void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) +{ + coord_t total_z_offset = path.z_offset + position.z_; + Application::getInstance().communication_->sendLineTo( + path.config.type, + position + Point3LL(0, 0, z_ + total_z_offset), + path.getLineWidthForLayerView(), + path.config.getLayerThickness() + total_z_offset, + extrude_speed); +} + void LayerPlan::writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset) { gcode.writeTravel(position + Point3LL(0, 0, z_ + path_z_offset), speed); @@ -2330,8 +2341,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) insertTempOnTime(time, path_idx); const double extrude_speed = speed * path.speed_back_pressure_factor; - communication - ->sendLineTo(path.config.type, path.points[point_idx].toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); writeExtrusionRelativeZ( gcode, path.points[point_idx], @@ -2340,6 +2349,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) path.getExtrusionMM3perMM(), path.config.type, update_extrusion_offset); + sendLineTo(path, path.points[point_idx], extrude_speed); prev_point = path.points[point_idx]; } @@ -2375,12 +2385,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.setZ(std::round(z_ + layer_thickness_ * length / totalLength)); const double extrude_speed = speed * spiral_path.speed_back_pressure_factor; - communication->sendLineTo( - spiral_path.config.type, - spiral_path.points[point_idx].toPoint2LL(), - spiral_path.getLineWidthForLayerView(), - spiral_path.config.getLayerThickness(), - extrude_speed); writeExtrusionRelativeZ( gcode, spiral_path.points[point_idx], @@ -2389,6 +2393,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, update_extrusion_offset); + sendLineTo(spiral_path, spiral_path.points[point_idx], extrude_speed); } // for layer display only - the loop finished at the seam vertex but as we started from // the location of the previous layer's seam vertex the loop may have a gap if this layer's @@ -2399,12 +2404,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // vertex would not be shifted (as it's the last vertex in the sequence). The smoother the model, // the less the vertices are shifted and the less obvious is the ridge. If the layer display // really displayed a spiral rather than slices of a spiral, this would not be required. - communication->sendLineTo( - spiral_path.config.type, - spiral_path.points[0].toPoint2LL(), - spiral_path.getLineWidthForLayerView(), - spiral_path.config.getLayerThickness(), - speed); + sendLineTo(spiral_path, spiral_path.points[0], speed); } path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path } @@ -2584,19 +2584,18 @@ bool LayerPlan::writePathWithCoasting( Point3LL prev_pt = gcode.getPositionXY(); { // write normal extrude path: - Communication* communication = Application::getInstance().communication_; for (size_t point_idx = 0; point_idx <= point_idx_before_start; point_idx++) { auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); insertTempOnTime(time, path_idx); - communication->sendLineTo(path.config.type, path.points[point_idx].toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, path.points[point_idx], extrude_speed); prev_pt = path.points[point_idx]; } - communication->sendLineTo(path.config.type, start.toPoint2LL(), path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, start, extrude_speed); } // write coasting path diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 1a75fe2516..e6c14098a5 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -67,7 +67,7 @@ class ArcusCommunication::PathCompiler std::vector points; //!< The points used to define the line segments, the size of this vector is D*(N+1) as each line segment is defined from one point to the next. D is //!< the dimensionality of the point. - Point2LL last_point; + Point3LL last_point; PathCompiler(const PathCompiler&) = delete; PathCompiler& operator=(const PathCompiler&) = delete; @@ -80,13 +80,12 @@ class ArcusCommunication::PathCompiler : _cs_private_data(cs_private_data) , _layer_nr(0) , extruder(0) - , data_point_type(cura::proto::PathSegment::Point2D) + , data_point_type(cura::proto::PathSegment::Point3D) , line_types() , line_widths() , line_thicknesses() , line_velocities() , points() - , last_point{ 0, 0 } { } @@ -143,11 +142,11 @@ class ArcusCommunication::PathCompiler * of the path this jump is marked as `PrintFeatureType::NoneType`. * \param from The initial point of a polygon. */ - void handleInitialPoint(const Point2LL& initial_point) + void handleInitialPoint(const Point3LL& initial_point) { if (points.size() == 0) { - addPoint2D(initial_point); + addPoint3D(initial_point); } else if (initial_point != last_point) { @@ -201,7 +200,7 @@ class ArcusCommunication::PathCompiler /*! * \brief Move the current point of this path to \p position. */ - void setCurrentPosition(const Point2LL& position) + void setCurrentPosition(const Point3LL& position) { handleInitialPoint(position); } @@ -217,7 +216,7 @@ class ArcusCommunication::PathCompiler * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - void sendLineTo(const PrintFeatureType& print_feature_type, const Point2LL& to, const coord_t& width, const coord_t& thickness, const Velocity& feedrate) + void sendLineTo(const PrintFeatureType& print_feature_type, const Point3LL& to, const coord_t& width, const coord_t& thickness, const Velocity& feedrate) { assert(! points.empty() && "A point must already be in the buffer for sendLineTo(.) to function properly."); @@ -231,13 +230,14 @@ class ArcusCommunication::PathCompiler /*! * \brief Convert and add a point to the points buffer. * - * Each point is represented as two consecutive floats. All members adding a - * 2D point to the data should use this function. + * Each point is represented as three consecutive floats. All members adding a + * 3D point to the data should use this function. */ - void addPoint2D(const Point2LL& point) + void addPoint3D(const Point3LL& point) { - points.push_back(INT2MM(point.X)); - points.push_back(INT2MM(point.Y)); + points.push_back(INT2MM(point.x_)); + points.push_back(INT2MM(point.y_)); + points.push_back(INT2MM(point.z_)); last_point = point; } @@ -253,9 +253,9 @@ class ArcusCommunication::PathCompiler * \param thickness The layer thickness of the polygon. * \param velocity How fast the polygon is printed. */ - void addLineSegment(const PrintFeatureType& print_feature_type, const Point2LL& point, const coord_t& width, const coord_t& thickness, const Velocity& velocity) + void addLineSegment(const PrintFeatureType& print_feature_type, const Point3LL& point, const coord_t& width, const coord_t& thickness, const Velocity& velocity) { - addPoint2D(point); + addPoint3D(point); line_types.push_back(print_feature_type); line_widths.push_back(INT2MM(width)); line_thicknesses.push_back(INT2MM(thickness)); @@ -345,7 +345,7 @@ bool ArcusCommunication::hasSlice() const && private_data->slice_count < 1; // Only slice once per run of CuraEngine. See documentation of slice_count. } -void ArcusCommunication::sendCurrentPosition(const Point2LL& position) +void ArcusCommunication::sendCurrentPosition(const Point3LL& position) { path_compiler->setCurrentPosition(position); } @@ -379,7 +379,7 @@ void ArcusCommunication::sendLayerComplete(const LayerIndex::value_type& layer_n layer->set_thickness(thickness); } -void ArcusCommunication::sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) +void ArcusCommunication::sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) { path_compiler->sendLineTo(type, to, line_width, line_thickness, velocity); } diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 269978ff7f..496ff681e7 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -56,7 +56,7 @@ void CommandLine::beginGCode() void CommandLine::flushGCode() { } -void CommandLine::sendCurrentPosition(const Point2LL&) +void CommandLine::sendCurrentPosition(const Point3LL &) { } void CommandLine::sendFinishedSlicing() const @@ -65,7 +65,7 @@ void CommandLine::sendFinishedSlicing() const void CommandLine::sendLayerComplete(const LayerIndex::value_type&, const coord_t&, const coord_t&) { } -void CommandLine::sendLineTo(const PrintFeatureType&, const Point2LL&, const coord_t&, const coord_t&, const Velocity&) +void CommandLine::sendLineTo(const PrintFeatureType&, const Point3LL &, const coord_t&, const coord_t&, const Velocity&) { } void CommandLine::sendOptimizedLayerData() diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 1de0935cb7..938980e456 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -975,7 +975,7 @@ void GCodeExport::writeTravel(const coord_t x, const coord_t y, const coord_t z, const PrintFeatureType travel_move_type = extruder_attr_[current_extruder_].retraction_e_amount_current_ ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing; const int display_width = extruder_attr_[current_extruder_].retraction_e_amount_current_ ? MM2INT(0.2) : MM2INT(0.1); const double layer_height = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("layer_height"); - Application::getInstance().communication_->sendLineTo(travel_move_type, Point2LL(x, y), display_width, layer_height, speed); + Application::getInstance().communication_->sendLineTo(travel_move_type, Point3LL(x, y, z), display_width, layer_height, speed); *output_stream_ << "G0"; writeFXYZE(speed, x, y, z, current_e_value_, travel_move_type); @@ -1248,10 +1248,12 @@ void GCodeExport::writeZhopStart(const coord_t hop_height, Velocity speed /*= 0* const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[current_extruder_]; speed = extruder.settings_.get("speed_z_hop"); } + const coord_t target_z = current_layer_z_ + is_z_hopped_; is_z_hopped_ = hop_height; current_speed_ = speed; - *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ current_layer_z_ + is_z_hopped_ } << new_line_; - total_bounding_box_.includeZ(current_layer_z_ + is_z_hopped_); + *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ target_z } << new_line_; + Application::getInstance().communication_->sendLineTo(PrintFeatureType::MoveRetraction, Point3LL(current_position_.x_, current_position_.y_, target_z), 0, 0, speed); + total_bounding_box_.includeZ(target_z); assert(speed > 0.0 && "Z hop speed should be positive."); } } @@ -1269,6 +1271,8 @@ void GCodeExport::writeZhopEnd(Velocity speed /*= 0*/) current_position_.z_ = current_layer_z_; current_speed_ = speed; *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ current_layer_z_ } << new_line_; + Application::getInstance() + .communication_->sendLineTo(PrintFeatureType::MoveRetraction, Point3LL(current_position_.x_, current_position_.y_, current_layer_z_), 0, 0, speed); assert(speed > 0.0 && "Z hop speed should be positive."); } } From 7bbbbf76516dce0c6820328adce356a4103503f6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 09:27:38 +0200 Subject: [PATCH 012/139] Create a scarf seam depending on the given settings CURA-12081 --- include/InsetOrderOptimizer.h | 4 +- include/LayerPlan.h | 16 +- include/geometry/Point2LL.h | 2 +- include/geometry/Point3LL.h | 5 + include/gradual_flow/FlowLimitedPath.h | 23 +- include/gradual_flow/Processor.h | 19 +- include/pathPlanning/GCodePath.h | 1 + src/FffGcodeWriter.cpp | 6 +- src/InsetOrderOptimizer.cpp | 10 +- src/LayerPlan.cpp | 325 ++++++++++++++++--------- 10 files changed, 262 insertions(+), 149 deletions(-) diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index 3a28040376..eb76f4d85e 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -57,7 +57,8 @@ class InsetOrderOptimizer const ZSeamConfig& z_seam_config, const std::vector& paths, const Point2LL& model_center_point, - const Shape& disallowed_areas_for_seams = {}); + const Shape& disallowed_areas_for_seams = {}, + const bool scarf_seam = false); /*! * Adds the insets to the given layer plan. @@ -110,6 +111,7 @@ class InsetOrderOptimizer const LayerIndex layer_nr_; const Point2LL model_center_point_; // Center of the model (= all meshes) axis-aligned bounding-box. Shape disallowed_areas_for_seams_; + const bool scarf_seam_; std::vector> inset_polys_; // vector of vectors holding the inset polygons Shape retraction_region_; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 788aee555d..aee8a952ee 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -359,14 +359,15 @@ class LayerPlan : public NoCopy * \param fan_speed Fan speed override for this path. */ void addExtrusionMove( - const Point2LL p, + const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, const Ratio width_factor = 1.0_r, const bool spiralize = false, const Ratio speed_factor = 1.0_r, - const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, + const bool travel_to_z = true); /*! * Add polygon to the gcode starting at vertex \p startIdx @@ -451,8 +452,8 @@ class LayerPlan : public NoCopy * the first bridge segment. */ void addWallLine( - const Point2LL& p0, - const Point2LL& p1, + const Point3LL& p0, + const Point3LL& p1, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -461,7 +462,8 @@ class LayerPlan : public NoCopy const Ratio width_factor, double& non_bridge_line_volume, Ratio speed_factor, - double distance_to_bridge_start); + double distance_to_bridge_start, + const bool travel_to_z = true); /*! * Add a wall to the g-code starting at vertex \p start_idx @@ -522,7 +524,9 @@ class LayerPlan : public NoCopy bool always_retract, const bool is_closed, const bool is_reversed, - const bool is_linked_path); + const bool is_linked_path, + coord_t scarf_seam_length = 0, + Ratio scarf_seam_start_ratio = 1.0); /*! * Add an infill wall to the g-code diff --git a/include/geometry/Point2LL.h b/include/geometry/Point2LL.h index ffd213ea33..91b26c6ab6 100644 --- a/include/geometry/Point2LL.h +++ b/include/geometry/Point2LL.h @@ -220,7 +220,7 @@ Point3LL operator-(const Point3LL& p3, const Point2LL& p2); Point3LL& operator-=(Point3LL& p3, const Point2LL& p2); -inline Point2LL operator-(const Point2LL& p2, const Point3LL& p3); +Point2LL operator-(const Point2LL& p2, const Point3LL& p3); } // namespace cura diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index 1fe449205b..0e4b62ed7b 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -133,6 +133,11 @@ class Point3LL return x_ * x_ + y_ * y_ + z_ * z_; } + [[nodiscard]] double vSize2f() + { + return static_cast(x_) * static_cast(x_) + static_cast(y_) * static_cast(y_) + static_cast(z_) * static_cast(z_); + } + [[nodiscard]] coord_t vSize() const { return std::llrint(sqrt(static_cast(vSize2()))); diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index 7a8219e678..c1f3864c94 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -30,7 +30,7 @@ enum class FlowState struct FlowLimitedPath { const GCodePath* original_gcode_path_data; - PointsSet points{}; + std::vector points{}; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -102,7 +102,7 @@ struct FlowLimitedPath for (auto point : points) { const auto identifier = is_first_point ? "M" : "L"; - path_data += fmt::format("{}{} {} ", identifier, point.X * 1e-3, point.Y * 1e-3); + path_data += fmt::format("{}{} {} ", identifier, point.x_ * 1e-3, point.y_ * 1e-3); is_first_point = false; } return path_data; @@ -140,7 +140,7 @@ struct FlowLimitedPath auto last_point = points.front(); for (const auto& point : points | ranges::views::drop(1)) { - path_length += std::hypot(point.X - last_point.X, point.Y - last_point.Y); + path_length += std::hypot(point.x_ - last_point.x_, point.y_ - last_point.y_); last_point = point; } } @@ -182,12 +182,12 @@ struct FlowLimitedPath auto current_partition_duration = 0.0; auto partition_index = direction == utils::Direction::Forward ? 0 : points.size() - 1; auto iteration_direction = direction == utils::Direction::Forward ? 1 : -1; - auto prev_point = points[partition_index]; + Point3LL prev_point = points[partition_index]; while (true) { - const auto next_point = points[partition_index + iteration_direction]; - const auto segment_length = std::hypot(next_point.X - prev_point.X, next_point.Y - prev_point.Y); + const Point3LL next_point = points[partition_index + iteration_direction]; + const auto segment_length = std::hypot(next_point.x_ - prev_point.x_, next_point.y_ - prev_point.y_); const auto segment_duration = segment_length / partition_speed; if (current_partition_duration + segment_duration < partition_duration) @@ -201,9 +201,10 @@ struct FlowLimitedPath const auto duration_left = partition_duration - current_partition_duration; auto segment_ratio = duration_left / segment_duration; assert(segment_ratio >= -1e-6 && segment_ratio <= 1. + 1e-6); - const auto partition_x = prev_point.X + static_cast(static_cast(next_point.X - prev_point.X) * segment_ratio); - const auto partition_y = prev_point.Y + static_cast(static_cast(next_point.Y - prev_point.Y) * segment_ratio); - const auto partition_point = ClipperLib::IntPoint(partition_x, partition_y); + const auto partition_x = prev_point.x_ + static_cast(static_cast(next_point.x_ - prev_point.x_) * segment_ratio); + const auto partition_y = prev_point.y_ + static_cast(static_cast(next_point.y_ - prev_point.y_) * segment_ratio); + const auto partition_z = prev_point.z_ + static_cast(static_cast(next_point.z_ - prev_point.z_) * segment_ratio); + const Point3LL partition_point(partition_x, partition_y, partition_z); /* * partition point @@ -230,7 +231,7 @@ struct FlowLimitedPath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - PointsSet left_points; + std::vector left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -238,7 +239,7 @@ struct FlowLimitedPath left_points.emplace_back(partition_point); // points right of the partition_index - PointsSet right_points; + std::vector right_points; right_points.emplace_back(partition_point); for (unsigned int i = partition_point_index; i < points.size(); ++i) { diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index 48998a6d51..4954473959 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -32,19 +32,7 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ // Process first path for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - PointsSet points; - - points.reserve(path.points.size()); - std::transform( - path.points.begin(), - path.points.end(), - std::back_inserter(points), - [](const Point3LL& point) - { - return point.toPoint2LL(); - }); - - gcode_paths.push_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.push_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = path.points }); } /* Process remaining paths @@ -58,8 +46,9 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - PointsSet points{ gcode_paths.back().points.back() }; + std::vector points{ gcode_paths.back().points.back() }; +#warning this is probably not necessary points.reserve(path.points.size() + 1); std::transform( path.points.begin(), @@ -67,7 +56,7 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ std::back_inserter(points), [](const Point3LL& point) { - return point.toPoint2LL(); + return point; }); gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = std::move(points) }); diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 9780d1bda8..2eaf050d2e 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -50,6 +50,7 @@ struct GCodePath bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise TimeMaterialEstimates estimates{}; //!< Naive time and material estimates + bool travel_to_z{ true }; //! Indicates whether we should add a travel move to the Z height of the first point before processing the path /*! * Whether this config is the config of a travel path. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 552f1c9c60..dee067192a 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2735,6 +2735,8 @@ bool FffGcodeWriter::processInsets( mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); + constexpr Shape disallowed_areas_for_seams; + constexpr bool scarf_seam = true; InsetOrderOptimizer wall_orderer( *this, storage, @@ -2754,7 +2756,9 @@ bool FffGcodeWriter::processInsets( mesh.settings.get("wall_x_extruder_nr").extruder_nr_, z_seam_config, part.wall_toolpaths, - mesh.bounding_box.flatten().getMiddle()); + mesh.bounding_box.flatten().getMiddle(), + disallowed_areas_for_seams, + scarf_seam); added_something |= wall_orderer.addToLayer(); } return added_something; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index ad036f62b9..ca6bc091b8 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -52,7 +52,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( const ZSeamConfig& z_seam_config, const std::vector& paths, const Point2LL& model_center_point, - const Shape& disallowed_areas_for_seams) + const Shape& disallowed_areas_for_seams, + const bool scarf_seam) : gcode_writer_(gcode_writer) , storage_(storage) , gcode_layer_(gcode_layer) @@ -74,6 +75,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( , layer_nr_(gcode_layer.getLayerNr()) , model_center_point_(model_center_point) , disallowed_areas_for_seams_{ disallowed_areas_for_seams } + , scarf_seam_(scarf_seam) { } @@ -146,6 +148,8 @@ bool InsetOrderOptimizer::addToLayer() const GCodePathConfig& bridge_config = is_outer_wall ? inset_0_bridge_config_ : inset_X_bridge_config_; const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist_ : wall_x_wipe_dist_; const bool retract_before = is_outer_wall ? retract_before_outer_wall_ : false; + const coord_t scarf_seam_length = scarf_seam_ && is_outer_wall ? settings_.get("scarf_joint_seam_length") : 0; + const Ratio scarf_seam_start_ratio = scarf_seam_ && is_outer_wall ? settings_.get("scarf_joint_seam_start_height_ratio") : 1.0_r; const bool revert_inset = alternate_walls && (path.vertices_->inset_idx_ % 2 != 0); const bool revert_layer = alternate_walls && (layer_nr_ % 2 != 0); @@ -166,7 +170,9 @@ bool InsetOrderOptimizer::addToLayer() retract_before, path.is_closed_, backwards, - linked_path); + linked_path, + scarf_seam_length, + scarf_seam_start_ratio); added_something = true; } return added_something; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9ef5d1276e..c275732c96 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -531,23 +531,25 @@ void LayerPlan::planPrime(double prime_blob_wipe_length) } void LayerPlan::addExtrusionMove( - const Point2LL p, + const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor, - const double fan_speed) + const double fan_speed, + const bool travel_to_z) { GCodePath* path = getLatestPathWithConfig(config, space_fill_type, config.z_offset, flow, width_factor, spiralize, speed_factor); path->points.push_back(p); path->setFanSpeed(fan_speed); + path->travel_to_z = travel_to_z; if (! static_cast(first_extrusion_acc_jerk_)) { first_extrusion_acc_jerk_ = std::make_pair(path->config.getAcceleration(), path->config.getJerk()); } - last_planned_position_ = p; + last_planned_position_ = p.toPoint2LL(); } void LayerPlan::addPolygon( @@ -647,8 +649,8 @@ void LayerPlan::addPolygonsByOptimizer( static constexpr double 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 Point2LL& p0, - const Point2LL& p1, + const Point3LL& p0, + const Point3LL& p1, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -657,7 +659,8 @@ void LayerPlan::addWallLine( const Ratio width_factor, double& non_bridge_line_volume, Ratio speed_factor, - double distance_to_bridge_start) + double distance_to_bridge_start, + const bool travel_to_z) { 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 @@ -668,7 +671,7 @@ void LayerPlan::addWallLine( const Ratio bridge_wall_coast = settings.get("bridge_wall_coast"); const Ratio overhang_speed_factor = settings.get("wall_overhang_speed_factor"); - Point2LL cur_point = p0; + Point3LL cur_point = p0; // helper function to add a single non-bridge line @@ -676,14 +679,14 @@ void LayerPlan::addWallLine( // alternatively, if the line follows a bridge line, it may be segmented and the print speed gradually increased to reduce under-extrusion - auto addNonBridgeLine = [&](const Point2LL& line_end) + auto addNonBridgeLine = [&](const Point3LL& line_end) { - coord_t distance_to_line_end = vSize(cur_point - line_end); + coord_t distance_to_line_end = (cur_point - line_end).vSize(); while (distance_to_line_end > min_line_len) { // if we are accelerating after a bridge line, the segment length is less than the whole line length - Point2LL segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) + Point3LL segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) ? line_end : cur_point + (line_end - cur_point) * acceleration_segment_len / distance_to_line_end; @@ -708,7 +711,7 @@ void LayerPlan::addWallLine( segment_end = line_end; } - const coord_t len = vSize(cur_point - segment_end); + const coord_t len = (cur_point - segment_end).vSize(); if (coast_dist > 0 && ((distance_to_bridge_start - len) <= coast_dist)) { if ((len - coast_dist) > min_line_len) @@ -721,7 +724,9 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - speed_factor); + speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } // then coast to start of bridge segment constexpr Ratio no_flow = 0.0_r; // Coasting has no flow rate. @@ -737,7 +742,10 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor + : overhang_speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } distance_to_bridge_start -= len; @@ -752,16 +760,19 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor + : overhang_speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } - non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * width_factor * speed_factor * default_config.getSpeed(); + non_bridge_line_volume += (cur_point - segment_end).vSize() * segment_flow * width_factor * speed_factor * default_config.getSpeed(); cur_point = segment_end; speed_factor = 1 - (1 - speed_factor) * acceleration_factor; if (speed_factor >= 0.9) { speed_factor = 1.0; } - distance_to_line_end = vSize(cur_point - line_end); + distance_to_line_end = (cur_point - line_end).vSize(); } }; @@ -773,7 +784,7 @@ void LayerPlan::addWallLine( // what part of the line segment will be printed with what config. return false; } - return PolygonUtils::polygonCollidesWithLineSegment(roofing_mask_, p0, p1) || roofing_mask_.inside(p1, true); + return PolygonUtils::polygonCollidesWithLineSegment(roofing_mask_, p0.toPoint2LL(), p1.toPoint2LL()) || roofing_mask_.inside(p1.toPoint2LL(), true); }(); if (use_roofing_config) @@ -785,7 +796,7 @@ void LayerPlan::addWallLine( // to the first and last point of the intersected line segments alternating between // roofing and default_config's. OpenLinesSet line_polys; - line_polys.addSegment(p0, p1); + line_polys.addSegment(p0.toPoint2LL(), p1.toPoint2LL()); constexpr bool restitch = false; // only a single line doesn't need stitching auto roofing_line_segments = roofing_mask_.intersection(line_polys, restitch); @@ -794,7 +805,7 @@ void LayerPlan::addWallLine( // roofing_line_segments should never be empty since we already checked that the line segment // intersects with the roofing area. But if it is empty then just print the line segment // using the default_config. - addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } else { @@ -823,16 +834,25 @@ void LayerPlan::addWallLine( // if the start of the line segment is not at minimum distance from p0 if (vSize2(line_poly.front() - p0) > min_line_len * min_line_len) { - addExtrusionMove(line_poly.front(), default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove( + line_poly.front(), + default_config, + SpaceFillType::Polygons, + flow, + width_factor, + spiralize, + 1.0_r, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } - addExtrusionMove(line_poly.back(), roofing_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(line_poly.back(), roofing_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } // if the last point is not yet at a minimum distance from p1 then add a move to p1 if (vSize2(roofing_line_segments.back().back() - p1) > min_line_len * min_line_len) { - addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } } } @@ -846,19 +866,21 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? 1.0_r : overhang_speed_factor); + (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? 1.0_r : overhang_speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } else { // bridges may be required - if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0, p1)) + if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.toPoint2LL(), p1.toPoint2LL())) { // the line crosses the boundary between supported and non-supported regions so one or more bridges are required // determine which segments of the line are bridges OpenLinesSet line_polys; - line_polys.addSegment(p0, p1); + line_polys.addSegment(p0.toPoint2LL(), p1.toPoint2LL()); constexpr bool restitch = false; // only a single line doesn't need stitching line_polys = bridge_wall_mask_.intersection(line_polys, restitch); @@ -868,10 +890,10 @@ void LayerPlan::addWallLine( { // find the bridge line segment that's nearest to the current point size_t nearest = 0; - double smallest_dist2 = vSize2f(cur_point - line_polys[0][0]); + double smallest_dist2 = (cur_point - line_polys[0][0]).vSize2f(); for (size_t i = 1; i < line_polys.size(); ++i) { - double dist2 = vSize2f(cur_point - line_polys[i][0]); + double dist2 = (cur_point - line_polys[i][0]).vSize2f(); if (dist2 < smallest_dist2) { nearest = i; @@ -881,10 +903,10 @@ void LayerPlan::addWallLine( const OpenPolyline& bridge = line_polys[nearest]; // set b0 to the nearest vertex and b1 the furthest - Point2LL b0 = bridge[0]; - Point2LL b1 = bridge[1]; + Point3LL b0 = bridge[0]; + Point3LL b1 = bridge[1]; - if (vSize2f(cur_point - b1) < vSize2f(cur_point - b0)) + if ((cur_point - b1).vSize2f() < (cur_point - b0).vSize2f()) { // swap vertex order b0 = bridge[1]; @@ -895,7 +917,7 @@ void LayerPlan::addWallLine( addNonBridgeLine(b0); - const double bridge_line_len = vSize(b1 - cur_point); + const double bridge_line_len = (b1 - cur_point).vSize(); if (bridge_line_len >= min_bridge_line_len) { @@ -903,7 +925,7 @@ void LayerPlan::addWallLine( if (bridge_line_len > min_line_len) { - addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor); + addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); non_bridge_line_volume = 0; cur_point = b1; // after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag @@ -924,7 +946,7 @@ void LayerPlan::addWallLine( // if we haven't yet reached p1, fill the gap with default_config line addNonBridgeLine(p1); } - else if (bridge_wall_mask_.inside(p0, true) && vSize(p0 - p1) >= min_bridge_line_len) + else if (bridge_wall_mask_.inside(p0.toPoint2LL(), true) && (p0 - p1).vSize() >= min_bridge_line_len) { // both p0 and p1 must be above air (the result will be ugly!) addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); @@ -983,11 +1005,13 @@ void LayerPlan::addWall( const GCodePathConfig& roofing_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, - double flow_ratio, + const double flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, - const bool is_linked_path) + const bool is_linked_path, + coord_t scarf_seam_length, + Ratio scarf_seam_start_ratio) { if (wall.empty()) { @@ -1091,6 +1115,7 @@ void LayerPlan::addWall( }; bool first_line = true; + bool first_scarf = true; const coord_t small_feature_max_length = settings.get("small_feature_max_length"); const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); @@ -1098,93 +1123,170 @@ void LayerPlan::addWall( small_feature_speed_factor = std::max((double)small_feature_speed_factor, (double)(min_speed / default_config.getSpeed())); const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! const coord_t max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); + const auto scarf_split_distance = settings.get("scarf_split_distance"); + const coord_t scarf_max_z_offset = -(1.0 - scarf_seam_start_ratio) * layer_thickness_; ExtrusionJunction p0 = wall[start_idx]; const int direction = is_reversed ? -1 : 1; const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); - for (size_t point_idx = 1; point_idx < max_index; point_idx++) + + auto addScarfedWall = [&](const bool is_scarf_closure) { - const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; + coord_t scarf_processed_distance = 0; - if (! bridge_wall_mask_.empty()) + for (size_t point_idx = 1; point_idx < max_index; point_idx++) { - computeDistanceToBridgeStart((wall.size() + start_idx + point_idx * direction - 1) % wall.size()); - } - - if (first_line) - { - addTravel(p0.p_, always_retract); - first_line = false; - } - - /* - If the line has variable width, break it up into pieces with the - following constraints: - - Each piece must be smaller than the Maximum Resolution setting. - - The difference between the trapezoidal shape of the line and the - rectangular shape of the line may not exceed the Maximum Extrusion - Area Deviation setting, unless required by the first constraint. - Since breaking up a line segment into N pieces (each with averaged - width) divides the area deviation by N, we can simply check how many - pieces we'd want to get low enough deviation, then check if each piece - is not too short at the end. - */ - const coord_t delta_line_width = p1.w_ - p0.w_; - const Point2LL line_vector = p1.p_ - p0.p_; - const coord_t line_length = vSize(line_vector); - /* - Calculate how much the line would deviate from the trapezoidal shape if printed at average width. - This formula is: - - Half the length times half the delta width, for the rectangular shape of the deviating side. - - Half of that because the ideal line width is trapezoidal, making the deviating part triangular. - - Double of that because the deviation occurs on both sides of the idealised line width. - This results in delta_line_width / 2 * line_length / 2 / 2 * 2 == delta_line_width * line_length / 4. - */ - const coord_t line_area_deviation = std::abs(delta_line_width) * line_length / 4; - const size_t pieces_limit_deviation = round_up_divide(line_area_deviation, max_area_deviation); // How many pieces we'd need to stay beneath the max area deviation. - const size_t pieces_limit_resolution = line_length / max_resolution; // Round down this time, to not exceed the maximum resolution. - const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. - const coord_t piece_length = round_divide(line_length, pieces); - - for (size_t piece = 0; piece < pieces; ++piece) - { - const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. - // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values - // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) - const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); - const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); - if (is_small_feature) + const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; + + if (! bridge_wall_mask_.empty()) { - constexpr bool spiralize = false; - addExtrusionMove( - destination, - default_config, - SpaceFillType::Polygons, - flow_ratio, - line_width * nominal_line_width_multiplier, - spiralize, - small_feature_speed_factor); + computeDistanceToBridgeStart((wall.size() + start_idx + point_idx * direction - 1) % wall.size()); } - else + + if (first_line) { - const Point2LL origin = p0.p_ + normal(line_vector, piece_length * piece); - addWallLine( - origin, - destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio, - line_width * nominal_line_width_multiplier, - non_bridge_line_volume, - speed_factor, - distance_to_bridge_start); + addTravel(p0.p_, always_retract); + first_line = false; } + + /* + If the line has variable width, break it up into pieces with the + following constraints: + - Each piece must be smaller than the Maximum Resolution setting. + - The difference between the trapezoidal shape of the line and the + rectangular shape of the line may not exceed the Maximum Extrusion + Area Deviation setting, unless required by the first constraint. + Since breaking up a line segment into N pieces (each with averaged + width) divides the area deviation by N, we can simply check how many + pieces we'd want to get low enough deviation, then check if each piece + is not too short at the end. + */ + const coord_t delta_line_width = p1.w_ - p0.w_; + const Point2LL line_vector = p1.p_ - p0.p_; + const coord_t line_length = vSize(line_vector); + /* + Calculate how much the line would deviate from the trapezoidal shape if printed at average width. + This formula is: + - Half the length times half the delta width, for the rectangular shape of the deviating side. + - Half of that because the ideal line width is trapezoidal, making the deviating part triangular. + - Double of that because the deviation occurs on both sides of the idealised line width. + This results in delta_line_width / 2 * line_length / 2 / 2 * 2 == delta_line_width * line_length / 4. + */ + const coord_t line_area_deviation = std::abs(delta_line_width) * line_length / 4; + const size_t pieces_limit_deviation = round_up_divide(line_area_deviation, max_area_deviation); // How many pieces we'd need to stay beneath the max area deviation. + const size_t pieces_limit_resolution = line_length / max_resolution; // Round down this time, to not exceed the maximum resolution. + const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. + const coord_t piece_length = round_divide(line_length, pieces); + + for (size_t piece = 0; piece < pieces; ++piece) + { + const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. + // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values + // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) + const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); + const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); + if (is_small_feature && ! is_scarf_closure) + { + constexpr bool spiralize = false; + addExtrusionMove( + destination, + default_config, + SpaceFillType::Polygons, + flow_ratio, + line_width * nominal_line_width_multiplier, + spiralize, + small_feature_speed_factor); + } + else + { + coord_t piece_processed_distance = 0; + + // Cut piece into smaller parts for scarf seam + while (scarf_processed_distance < scarf_seam_length && piece_processed_distance < piece_length) + { + const double scarf_factor_origin = static_cast(scarf_processed_distance) / static_cast(scarf_seam_length); + Point3LL scarf_origin = p0.p_ + normal(line_vector, piece_length * piece + piece_processed_distance); + if (! is_scarf_closure) + { + scarf_origin.z_ = std::lerp(scarf_max_z_offset, 0.0, scarf_factor_origin); + } + + coord_t length_to_process = std::min({ scarf_seam_length - scarf_processed_distance, piece_length - piece_processed_distance, scarf_split_distance }); + const double scarf_factor_destination = static_cast(scarf_processed_distance + length_to_process) / static_cast(scarf_seam_length); + Point3LL scarf_destination = scarf_origin + normal(line_vector, length_to_process); + if (! is_scarf_closure) + { + scarf_destination.z_ = std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination); + } + + const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; + double scarf_segment_flow_ratio; + if (is_scarf_closure) + { + scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); + } + else + { + scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); + } + + if (first_scarf) + { + // Manually add a Z-only travel move to set the nozzle at the height of the first point + addTravel(p0.p_, always_retract, scarf_origin.z_); + first_scarf = false; + } + + constexpr bool travel_to_z = false; + addWallLine( + scarf_origin, + scarf_destination, + settings, + default_config, + roofing_config, + bridge_config, + flow_ratio * scarf_segment_flow_ratio, + line_width * nominal_line_width_multiplier, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start, + travel_to_z); + + piece_processed_distance += length_to_process; + scarf_processed_distance += length_to_process; + } + + if (piece_processed_distance < piece_length && ! is_scarf_closure) + { + const Point2LL origin = p0.p_ + normal(line_vector, piece_length * piece + piece_processed_distance); + addWallLine( + origin, + destination, + settings, + default_config, + roofing_config, + bridge_config, + flow_ratio, + line_width * nominal_line_width_multiplier, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start); + } + } + } + + p0 = p1; } + }; - p0 = p1; + // First pass to add the wall with the scarf beginning + addScarfedWall(false); + + if (scarf_seam_length) + { + // Second pass to add the scarf closure + addScarfedWall(true); } if (wall.size() >= 2) @@ -1499,12 +1601,11 @@ void LayerPlan::addLinesInGivenOrder( void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) { - coord_t total_z_offset = path.z_offset + position.z_; Application::getInstance().communication_->sendLineTo( path.config.type, - position + Point3LL(0, 0, z_ + total_z_offset), + position + Point3LL(0, 0, z_ + path.z_offset), path.getLineWidthForLayerView(), - path.config.getLayerThickness() + total_z_offset, + path.config.getLayerThickness() + path.z_offset + position.z_, extrude_speed); } @@ -2285,7 +2386,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeComment(ss.str()); } - if (! path.spiralize && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset + path.points.front().z_ != gcode.getPositionZ()) + if (! path.spiralize && path.travel_to_z && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset + path.points.front().z_ != gcode.getPositionZ()) && (path_idx > 0 || layer_nr_ > 0)) { // First move to desired height to then make a plain horizontal move From 85220697c96142e1aa120d610709d38017eb2f80 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 09:55:17 +0200 Subject: [PATCH 013/139] Fix scarf seam for small walls CURA-12081 --- src/LayerPlan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c275732c96..18d1f5362f 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1125,6 +1125,7 @@ void LayerPlan::addWall( const coord_t max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); const auto scarf_split_distance = settings.get("scarf_split_distance"); const coord_t scarf_max_z_offset = -(1.0 - scarf_seam_start_ratio) * layer_thickness_; + scarf_seam_length = std::min(scarf_seam_length, wall.length()); ExtrusionJunction p0 = wall[start_idx]; From 026557b66a307da4376c3aabef4916042086c13e Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 22 Aug 2024 07:56:11 +0000 Subject: [PATCH 014/139] Applied clang-format. --- include/ExtruderPlan.h | 2 +- include/communication/ArcusCommunication.h | 2 +- src/communication/CommandLine.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index d82fb34b1a..859f91a51f 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -190,7 +190,7 @@ class ExtruderPlan /*! * @return distance between p0 and p1 as well as the time spend on the segment */ - std::pair getPointToPointTime(const Point3LL &p0, const Point3LL &p1, const GCodePath& path); + std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path); /*! * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. diff --git a/include/communication/ArcusCommunication.h b/include/communication/ArcusCommunication.h index f6a8c81f66..913c38418f 100644 --- a/include/communication/ArcusCommunication.h +++ b/include/communication/ArcusCommunication.h @@ -86,7 +86,7 @@ class ArcusCommunication : public Communication * This may indicate the starting position (or any other jump in the path). * \param position The current position to start the next line at. */ - void sendCurrentPosition(const Point3LL &position) override; + void sendCurrentPosition(const Point3LL& position) override; /* * \brief Sends a message to indicate that all the slicing is done. diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 496ff681e7..38a8f16734 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -56,7 +56,7 @@ void CommandLine::beginGCode() void CommandLine::flushGCode() { } -void CommandLine::sendCurrentPosition(const Point3LL &) +void CommandLine::sendCurrentPosition(const Point3LL&) { } void CommandLine::sendFinishedSlicing() const @@ -65,7 +65,7 @@ void CommandLine::sendFinishedSlicing() const void CommandLine::sendLayerComplete(const LayerIndex::value_type&, const coord_t&, const coord_t&) { } -void CommandLine::sendLineTo(const PrintFeatureType&, const Point3LL &, const coord_t&, const coord_t&, const Velocity&) +void CommandLine::sendLineTo(const PrintFeatureType&, const Point3LL&, const coord_t&, const coord_t&, const Velocity&) { } void CommandLine::sendOptimizedLayerData() From fb36ac463b1e70e7fced5cd424730a2f4ca6c742 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 12:45:37 +0200 Subject: [PATCH 015/139] Scarf seam calculation optimization CURA-12081 --- src/LayerPlan.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 18d1f5362f..18a64abf7d 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1132,6 +1132,10 @@ void LayerPlan::addWall( const int direction = is_reversed ? -1 : 1; const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); + double scarf_factor_origin = 0.0; + Point3LL scarf_origin = p0.p_; + scarf_origin.z_ = scarf_max_z_offset; + auto addScarfedWall = [&](const bool is_scarf_closure) { coord_t scarf_processed_distance = 0; @@ -1206,13 +1210,6 @@ void LayerPlan::addWall( // Cut piece into smaller parts for scarf seam while (scarf_processed_distance < scarf_seam_length && piece_processed_distance < piece_length) { - const double scarf_factor_origin = static_cast(scarf_processed_distance) / static_cast(scarf_seam_length); - Point3LL scarf_origin = p0.p_ + normal(line_vector, piece_length * piece + piece_processed_distance); - if (! is_scarf_closure) - { - scarf_origin.z_ = std::lerp(scarf_max_z_offset, 0.0, scarf_factor_origin); - } - coord_t length_to_process = std::min({ scarf_seam_length - scarf_processed_distance, piece_length - piece_processed_distance, scarf_split_distance }); const double scarf_factor_destination = static_cast(scarf_processed_distance + length_to_process) / static_cast(scarf_seam_length); Point3LL scarf_destination = scarf_origin + normal(line_vector, length_to_process); @@ -1256,6 +1253,8 @@ void LayerPlan::addWall( piece_processed_distance += length_to_process; scarf_processed_distance += length_to_process; + scarf_origin = scarf_destination; + scarf_factor_origin = scarf_factor_destination; } if (piece_processed_distance < piece_length && ! is_scarf_closure) From 5a184f30f5d38222c7e9a5f4bcd7a4e1355c3ca3 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 12:46:00 +0200 Subject: [PATCH 016/139] Gradual flow calculation optimization CURA-12081 --- include/gradual_flow/Processor.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index 4954473959..c04b0c4734 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -47,17 +47,7 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { std::vector points{ gcode_paths.back().points.back() }; - -#warning this is probably not necessary - points.reserve(path.points.size() + 1); - std::transform( - path.points.begin(), - path.points.end(), - std::back_inserter(points), - [](const Point3LL& point) - { - return point; - }); + points.insert(points.end(), path.points.begin(), path.points.end()); gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = std::move(points) }); } From 816d3feca28991cb44c47a55e56d2ccf0b2b47b6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 12:49:11 +0200 Subject: [PATCH 017/139] Fix scarf seam after optimization CURA-12081 --- src/LayerPlan.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 18a64abf7d..764247a7e6 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1132,13 +1132,15 @@ void LayerPlan::addWall( const int direction = is_reversed ? -1 : 1; const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); - double scarf_factor_origin = 0.0; - Point3LL scarf_origin = p0.p_; - scarf_origin.z_ = scarf_max_z_offset; - auto addScarfedWall = [&](const bool is_scarf_closure) { coord_t scarf_processed_distance = 0; + double scarf_factor_origin = 0.0; + Point3LL scarf_origin = p0.p_; + if (! is_scarf_closure) + { + scarf_origin.z_ = scarf_max_z_offset; + } for (size_t point_idx = 1; point_idx < max_index; point_idx++) { From 86eabfb8de6c39ac70039452e990cf1e5b393189 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 13:38:20 +0200 Subject: [PATCH 018/139] Fix comment CURA-12081 --- include/gradual_flow/FlowLimitedPath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index c1f3864c94..49242f7130 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -327,7 +327,7 @@ struct GCodeState discretized_duration_remaining = 0; // set the current flow to the target end flow. When executing the backward pass we want to - // we start with this flow and gradually increase it to the target flow. However, if the + // start with this flow and gradually increase it to the target flow. However, if the // highest flow we can achieve is lower than this target flow we want to use that flow // instead. current_flow = std::min(current_flow, target_end_flow); From 2ff17e6147535adb657ebd90c7aa4fd090b917ba Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 22 Aug 2024 14:51:34 +0200 Subject: [PATCH 019/139] Retrieve scarf settings more consistently CURA-12081 --- include/LayerPlan.h | 3 +-- src/InsetOrderOptimizer.cpp | 6 ++---- src/LayerPlan.cpp | 7 ++++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index aee8a952ee..f4fe59fd32 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -525,8 +525,7 @@ class LayerPlan : public NoCopy const bool is_closed, const bool is_reversed, const bool is_linked_path, - coord_t scarf_seam_length = 0, - Ratio scarf_seam_start_ratio = 1.0); + const bool scarf_seam = false); /*! * Add an infill wall to the g-code diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index ca6bc091b8..e809405a60 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -148,8 +148,7 @@ bool InsetOrderOptimizer::addToLayer() const GCodePathConfig& bridge_config = is_outer_wall ? inset_0_bridge_config_ : inset_X_bridge_config_; const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist_ : wall_x_wipe_dist_; const bool retract_before = is_outer_wall ? retract_before_outer_wall_ : false; - const coord_t scarf_seam_length = scarf_seam_ && is_outer_wall ? settings_.get("scarf_joint_seam_length") : 0; - const Ratio scarf_seam_start_ratio = scarf_seam_ && is_outer_wall ? settings_.get("scarf_joint_seam_start_height_ratio") : 1.0_r; + const bool scarf_seam = scarf_seam_ && is_outer_wall; const bool revert_inset = alternate_walls && (path.vertices_->inset_idx_ % 2 != 0); const bool revert_layer = alternate_walls && (layer_nr_ % 2 != 0); @@ -171,8 +170,7 @@ bool InsetOrderOptimizer::addToLayer() path.is_closed_, backwards, linked_path, - scarf_seam_length, - scarf_seam_start_ratio); + scarf_seam); added_something = true; } return added_something; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 764247a7e6..932d4cf974 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1010,8 +1010,7 @@ void LayerPlan::addWall( const bool is_closed, const bool is_reversed, const bool is_linked_path, - coord_t scarf_seam_length, - Ratio scarf_seam_start_ratio) + const bool scarf_seam) { if (wall.empty()) { @@ -1022,6 +1021,7 @@ void LayerPlan::addWall( // make sure wall start point is not above air! start_idx = locateFirstSupportedVertex(wall, start_idx); } + const bool actual_scarf_seam = scarf_seam && is_closed; double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output double speed_factor = 1.0; // start first line at normal speed @@ -1123,9 +1123,10 @@ void LayerPlan::addWall( small_feature_speed_factor = std::max((double)small_feature_speed_factor, (double)(min_speed / default_config.getSpeed())); const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! const coord_t max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); + const coord_t scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const Ratio scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; const auto scarf_split_distance = settings.get("scarf_split_distance"); const coord_t scarf_max_z_offset = -(1.0 - scarf_seam_start_ratio) * layer_thickness_; - scarf_seam_length = std::min(scarf_seam_length, wall.length()); ExtrusionJunction p0 = wall[start_idx]; From 9d2f285033fe9969cb048358fd3c83d316554daf Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 07:58:20 +0200 Subject: [PATCH 020/139] Apply acceleration after seam CURA-12080 --- include/InsetOrderOptimizer.h | 4 +- include/LayerPlan.h | 3 +- src/FffGcodeWriter.cpp | 4 +- src/InsetOrderOptimizer.cpp | 8 ++- src/LayerPlan.cpp | 117 +++++++++++++++++++++++----------- 5 files changed, 95 insertions(+), 41 deletions(-) diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index eb76f4d85e..afba5dc452 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -58,7 +58,8 @@ class InsetOrderOptimizer const std::vector& paths, const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams = {}, - const bool scarf_seam = false); + const bool scarf_seam = false, + const bool smooth_speed = false); /*! * Adds the insets to the given layer plan. @@ -112,6 +113,7 @@ class InsetOrderOptimizer const Point2LL model_center_point_; // Center of the model (= all meshes) axis-aligned bounding-box. Shape disallowed_areas_for_seams_; const bool scarf_seam_; + const bool smooth_speed_; std::vector> inset_polys_; // vector of vectors holding the inset polygons Shape retraction_region_; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see diff --git a/include/LayerPlan.h b/include/LayerPlan.h index f4fe59fd32..65b0d56b98 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -525,7 +525,8 @@ class LayerPlan : public NoCopy const bool is_closed, const bool is_reversed, const bool is_linked_path, - const bool scarf_seam = false); + const bool scarf_seam = false, + const bool smooth_speed = false); /*! * Add an infill wall to the g-code diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index dee067192a..a7b8980adf 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2737,6 +2737,7 @@ bool FffGcodeWriter::processInsets( mesh.settings.get("wall_line_width_0") * 2); constexpr Shape disallowed_areas_for_seams; constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; InsetOrderOptimizer wall_orderer( *this, storage, @@ -2758,7 +2759,8 @@ bool FffGcodeWriter::processInsets( part.wall_toolpaths, mesh.bounding_box.flatten().getMiddle(), disallowed_areas_for_seams, - scarf_seam); + scarf_seam, + smooth_speed); added_something |= wall_orderer.addToLayer(); } return added_something; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index e809405a60..56961e2bde 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -53,7 +53,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( const std::vector& paths, const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams, - const bool scarf_seam) + const bool scarf_seam, + const bool smooth_speed) : gcode_writer_(gcode_writer) , storage_(storage) , gcode_layer_(gcode_layer) @@ -76,6 +77,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( , model_center_point_(model_center_point) , disallowed_areas_for_seams_{ disallowed_areas_for_seams } , scarf_seam_(scarf_seam) + , smooth_speed_(smooth_speed) { } @@ -149,6 +151,7 @@ bool InsetOrderOptimizer::addToLayer() const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist_ : wall_x_wipe_dist_; const bool retract_before = is_outer_wall ? retract_before_outer_wall_ : false; const bool scarf_seam = scarf_seam_ && is_outer_wall; + const bool smooth_speed = smooth_speed_ && is_outer_wall; const bool revert_inset = alternate_walls && (path.vertices_->inset_idx_ % 2 != 0); const bool revert_layer = alternate_walls && (layer_nr_ % 2 != 0); @@ -170,7 +173,8 @@ bool InsetOrderOptimizer::addToLayer() path.is_closed_, backwards, linked_path, - scarf_seam); + scarf_seam, + smooth_speed); added_something = true; } return added_something; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 932d4cf974..560b424e53 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -866,7 +866,7 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? 1.0_r : overhang_speed_factor, + (overhang_mask_.empty() || (! overhang_mask_.inside(p0.toPoint2LL(), true) && ! overhang_mask_.inside(p1.toPoint2LL(), true))) ? speed_factor : overhang_speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -1010,7 +1010,8 @@ void LayerPlan::addWall( const bool is_closed, const bool is_reversed, const bool is_linked_path, - const bool scarf_seam) + const bool scarf_seam, + const bool smooth_speed) { if (wall.empty()) { @@ -1120,13 +1121,19 @@ void LayerPlan::addWall( const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; - small_feature_speed_factor = std::max((double)small_feature_speed_factor, (double)(min_speed / default_config.getSpeed())); + small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! - const coord_t max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); - const coord_t scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); - const Ratio scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; + const auto max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); + const auto scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; const auto scarf_split_distance = settings.get("scarf_split_distance"); - const coord_t scarf_max_z_offset = -(1.0 - scarf_seam_start_ratio) * layer_thickness_; + const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); + const Ratio start_speed_ratio = smooth_speed ? settings.get("wall_0_start_speed_ratio") : 1.0_r; + const int acceleration = settings.get("wall_0_acceleration"); // mm/s² + const Velocity top_speed = default_config.getSpeed(); + const double start_speed = top_speed * start_speed_ratio; // mm/s + const coord_t accelerate_split_distance = settings.get("wall_0_speed_split_distance"); // mm + const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((std::pow(top_speed.value, 2) - std::pow(start_speed, 2)) / (2.0 * acceleration)) : 0; ExtrusionJunction p0 = wall[start_idx]; @@ -1135,14 +1142,18 @@ void LayerPlan::addWall( auto addScarfedWall = [&](const bool is_scarf_closure) { - coord_t scarf_processed_distance = 0; + coord_t scarf_remaining_distance = scarf_seam_length; double scarf_factor_origin = 0.0; - Point3LL scarf_origin = p0.p_; + + Point3LL split_origin = p0.p_; if (! is_scarf_closure) { - scarf_origin.z_ = scarf_max_z_offset; + split_origin.z_ = scarf_max_z_offset; } + coord_t accelerate_remaining_distance = accelerate_length; + double accelerate_factor_origin = 0.0; + for (size_t point_idx = 1; point_idx < max_index; point_idx++) { const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; @@ -1208,41 +1219,71 @@ void LayerPlan::addWall( } else { - coord_t piece_processed_distance = 0; + coord_t piece_remaining_distance = piece_length; // Cut piece into smaller parts for scarf seam - while (scarf_processed_distance < scarf_seam_length && piece_processed_distance < piece_length) + while ((scarf_remaining_distance > 0 || accelerate_remaining_distance > 0) && piece_remaining_distance > 0) { - coord_t length_to_process = std::min({ scarf_seam_length - scarf_processed_distance, piece_length - piece_processed_distance, scarf_split_distance }); - const double scarf_factor_destination = static_cast(scarf_processed_distance + length_to_process) / static_cast(scarf_seam_length); - Point3LL scarf_destination = scarf_origin + normal(line_vector, length_to_process); - if (! is_scarf_closure) + std::vector split_distances{ piece_remaining_distance }; + if (scarf_remaining_distance > 0) { - scarf_destination.z_ = std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination); + split_distances.push_back(scarf_remaining_distance); + split_distances.push_back(scarf_split_distance); } - - const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; - double scarf_segment_flow_ratio; - if (is_scarf_closure) + if (! is_scarf_closure && accelerate_remaining_distance > 0) { - scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); + split_distances.push_back(accelerate_remaining_distance); + split_distances.push_back(accelerate_split_distance); } - else + + coord_t length_to_process = *std::min_element(split_distances.begin(), split_distances.end()); + Point3LL split_destination = split_origin + normal(line_vector, length_to_process); + + double scarf_segment_flow_ratio = 1.0; + double scarf_factor_destination = 1.0; + if (scarf_remaining_distance > 0) { - scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); + scarf_factor_destination = 1.0 - (static_cast(scarf_remaining_distance - length_to_process) / static_cast(scarf_seam_length)); + if (! is_scarf_closure) + { + split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); + } + + const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; + double scarf_segment_flow_ratio; + if (is_scarf_closure) + { + scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); + } + else + { + scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); + } + + if (first_scarf) + { + // Manually add a Z-only travel move to set the nozzle at the height of the first point + addTravel(p0.p_, always_retract, split_origin.z_); + first_scarf = false; + } } - if (first_scarf) + double acceleration_speed_factor = 1.0; + double acceleration_factor_destination = 1.0; + if (accelerate_remaining_distance > 0) { - // Manually add a Z-only travel move to set the nozzle at the height of the first point - addTravel(p0.p_, always_retract, scarf_origin.z_); - first_scarf = false; + acceleration_factor_destination + = 1.0 - (static_cast(accelerate_remaining_distance - length_to_process) / static_cast(accelerate_length)); + + const double acceleration_factor_average = (accelerate_factor_origin + acceleration_factor_destination) / 2.0; + + acceleration_speed_factor = std::lerp(start_speed_ratio, 1.0, acceleration_factor_average); } constexpr bool travel_to_z = false; addWallLine( - scarf_origin, - scarf_destination, + split_origin, + split_destination, settings, default_config, roofing_config, @@ -1250,19 +1291,23 @@ void LayerPlan::addWall( flow_ratio * scarf_segment_flow_ratio, line_width * nominal_line_width_multiplier, non_bridge_line_volume, - speed_factor, + speed_factor * acceleration_speed_factor, distance_to_bridge_start, travel_to_z); - piece_processed_distance += length_to_process; - scarf_processed_distance += length_to_process; - scarf_origin = scarf_destination; + piece_remaining_distance -= length_to_process; + split_origin = split_destination; + + scarf_remaining_distance -= length_to_process; scarf_factor_origin = scarf_factor_destination; + + accelerate_remaining_distance -= length_to_process; + accelerate_factor_origin = acceleration_factor_destination; } - if (piece_processed_distance < piece_length && ! is_scarf_closure) + if (piece_remaining_distance > 0 && ! is_scarf_closure) { - const Point2LL origin = p0.p_ + normal(line_vector, piece_length * piece + piece_processed_distance); + const Point2LL origin = p0.p_ + normal(line_vector, piece_length * (piece + 1) - piece_remaining_distance); addWallLine( origin, destination, From fd1e268cb4ba9bf1512899acdc09572d2d86e341 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 09:21:03 +0200 Subject: [PATCH 021/139] Refactor wall line splitting CURA-12080 --- include/LayerPlan.h | 38 ++- src/LayerPlan.cpp | 594 ++++++++++++++++++++++++-------------------- 2 files changed, 357 insertions(+), 275 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 65b0d56b98..20b3023e82 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -482,7 +482,7 @@ class LayerPlan : public NoCopy */ void addWall( const Polygon& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -514,7 +514,7 @@ class LayerPlan : public NoCopy */ void addWall( const ExtrusionLine& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -679,7 +679,7 @@ class LayerPlan : public NoCopy * \return The index of the first supported vertex - if no vertices are supported, start_idx is returned */ template - unsigned locateFirstSupportedVertex(const T& wall, const unsigned start_idx) const + size_t locateFirstSupportedVertex(const T& wall, const size_t start_idx) const { if (bridge_wall_mask_.empty() && seam_overhang_mask_.empty()) { @@ -688,7 +688,7 @@ class LayerPlan : public NoCopy const auto air_below = bridge_wall_mask_.unionPolygons(seam_overhang_mask_); - unsigned curr_idx = start_idx; + size_t curr_idx = start_idx; while (true) { @@ -836,6 +836,36 @@ class LayerPlan : public NoCopy double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset = false); + + void addWallSplitted( + const ExtrusionLine& wall, + size_t start_idx, + const int direction, + const size_t max_index, + const Settings& settings, + const GCodePathConfig& default_config, + const GCodePathConfig& roofing_config, + const GCodePathConfig& bridge_config, + const double flow_ratio, + const Ratio nominal_line_width_multiplier, + double& non_bridge_line_volume, + const coord_t min_bridge_line_len, + const bool always_retract, + const bool is_small_feature, + Ratio small_feature_speed_factor, + const coord_t max_area_deviation, + const auto max_resolution, + const auto scarf_seam_length, + const auto scarf_seam_start_ratio, + const auto scarf_split_distance, + const coord_t scarf_max_z_offset, + const Ratio start_speed_ratio, + const coord_t accelerate_split_distance, + const coord_t accelerate_length, + const bool is_scarf_closure); + + // helper function to calculate the distance from the start of the current wall line to the first bridge segment + coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const; }; } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 560b424e53..82f5361157 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -962,7 +962,7 @@ void LayerPlan::addWallLine( void LayerPlan::addWall( const Polygon& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -997,356 +997,408 @@ void LayerPlan::addWall( addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } -void LayerPlan::addWall( +void LayerPlan::addWallSplitted( const ExtrusionLine& wall, - int start_idx, + size_t start_idx, + const int direction, + const size_t max_index, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, const GCodePathConfig& bridge_config, - coord_t wall_0_wipe_dist, const double flow_ratio, - bool always_retract, - const bool is_closed, - const bool is_reversed, - const bool is_linked_path, - const bool scarf_seam, - const bool smooth_speed) + const Ratio nominal_line_width_multiplier, + double& non_bridge_line_volume, + const coord_t min_bridge_line_len, + const bool always_retract, + const bool is_small_feature, + Ratio small_feature_speed_factor, + const coord_t max_area_deviation, + const auto max_resolution, + const auto scarf_seam_length, + const auto scarf_seam_start_ratio, + const auto scarf_split_distance, + const coord_t scarf_max_z_offset, + const Ratio start_speed_ratio, + const coord_t accelerate_split_distance, + const coord_t accelerate_length, + const bool is_scarf_closure) { - if (wall.empty()) - { - return; - } - if (is_closed) - { - // make sure wall start point is not above air! - start_idx = locateFirstSupportedVertex(wall, start_idx); - } - const bool actual_scarf_seam = scarf_seam && is_closed; + constexpr double speed_factor = 1.0; - double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output - double speed_factor = 1.0; // start first line at normal speed coord_t distance_to_bridge_start = 0; // will be updated before each line is processed + ExtrusionJunction p0 = wall[start_idx]; + bool first_line = ! is_scarf_closure; + bool first_split = ! is_scarf_closure; + Point3LL split_origin = p0.p_; + if (! is_scarf_closure) + { + split_origin.z_ = scarf_max_z_offset; + } - const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); - - const Ratio nominal_line_width_multiplier{ - 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } - }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + coord_t scarf_remaining_distance = scarf_seam_length; + double scarf_factor_origin = 0.0; - // helper function to calculate the distance from the start of the current wall line to the first bridge segment + coord_t accelerate_remaining_distance = accelerate_length; + double accelerate_factor_origin = 0.0; - auto computeDistanceToBridgeStart = [&](unsigned current_index) + for (size_t point_idx = 1; point_idx < max_index; point_idx++) { - distance_to_bridge_start = 0; + const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; if (! bridge_wall_mask_.empty()) { - // there is air below the part so iterate through the lines that have not yet been output accumulating the total distance to the first bridge segment - for (unsigned point_idx = current_index; point_idx < wall.size(); ++point_idx) + distance_to_bridge_start = computeDistanceToBridgeStart(wall, (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); + } + + if (first_line) + { + addTravel(p0.p_, always_retract); + first_line = false; + } + + /* + If the line has variable width, break it up into pieces with the + following constraints: + - Each piece must be smaller than the Maximum Resolution setting. + - The difference between the trapezoidal shape of the line and the + rectangular shape of the line may not exceed the Maximum Extrusion + Area Deviation setting, unless required by the first constraint. + Since breaking up a line segment into N pieces (each with averaged + width) divides the area deviation by N, we can simply check how many + pieces we'd want to get low enough deviation, then check if each piece + is not too short at the end. + */ + const coord_t delta_line_width = p1.w_ - p0.w_; + const Point2LL line_vector = p1.p_ - p0.p_; + const coord_t line_length = vSize(line_vector); + /* + Calculate how much the line would deviate from the trapezoidal shape if printed at average width. + This formula is: + - Half the length times half the delta width, for the rectangular shape of the deviating side. + - Half of that because the ideal line width is trapezoidal, making the deviating part triangular. + - Double of that because the deviation occurs on both sides of the idealised line width. + This results in delta_line_width / 2 * line_length / 2 / 2 * 2 == delta_line_width * line_length / 4. + */ + const coord_t line_area_deviation = std::abs(delta_line_width) * line_length / 4; + const size_t pieces_limit_deviation = round_up_divide(line_area_deviation, max_area_deviation); // How many pieces we'd need to stay beneath the max area deviation. + const size_t pieces_limit_resolution = line_length / max_resolution; // Round down this time, to not exceed the maximum resolution. + const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. + const coord_t piece_length = round_divide(line_length, pieces); + + for (size_t piece = 0; piece < pieces; ++piece) + { + const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. + // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values + // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) + const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); + const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); + if (is_small_feature && ! is_scarf_closure) + { + constexpr bool spiralize = false; + addExtrusionMove( + destination, + default_config, + SpaceFillType::Polygons, + flow_ratio, + line_width * nominal_line_width_multiplier, + spiralize, + small_feature_speed_factor); + } + else { - const ExtrusionJunction& p0 = wall[point_idx]; - const ExtrusionJunction& p1 = wall[(point_idx + 1) % wall.size()]; + coord_t piece_remaining_distance = piece_length; - if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.p_, p1.p_)) + // Cut piece into smaller parts for scarf seam + while ((scarf_remaining_distance > 0 || accelerate_remaining_distance > 0) && piece_remaining_distance > 0) { - // the line crosses the boundary between supported and non-supported regions so it will contain one or more bridge segments - - // determine which segments of the line are bridges + std::vector split_distances{ piece_remaining_distance }; + if (scarf_remaining_distance > 0) + { + split_distances.push_back(scarf_remaining_distance); + split_distances.push_back(scarf_split_distance); + } + if (! is_scarf_closure && accelerate_remaining_distance > 0) + { + split_distances.push_back(accelerate_remaining_distance); + split_distances.push_back(accelerate_split_distance); + } - OpenLinesSet line_polys; - line_polys.addSegment(p0.p_, p1.p_); - constexpr bool restitch = false; // only a single line doesn't need stitching - line_polys = bridge_wall_mask_.intersection(line_polys, restitch); + coord_t length_to_process = *std::min_element(split_distances.begin(), split_distances.end()); + Point3LL split_destination = split_origin + normal(line_vector, length_to_process); - while (line_polys.size() > 0) + double scarf_segment_flow_ratio = 1.0; + double scarf_factor_destination = 1.0; + if (scarf_remaining_distance > 0) { - // find the bridge line segment that's nearest to p0 - size_t nearest = 0; - double smallest_dist2 = vSize2f(p0.p_ - line_polys[0][0]); - for (unsigned i = 1; i < line_polys.size(); ++i) + scarf_factor_destination = 1.0 - (static_cast(scarf_remaining_distance - length_to_process) / static_cast(scarf_seam_length)); + if (! is_scarf_closure) { - double dist2 = vSize2f(p0.p_ - line_polys[i][0]); - if (dist2 < smallest_dist2) - { - nearest = i; - smallest_dist2 = dist2; - } + split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); } - const OpenPolyline& bridge = line_polys[nearest]; - // set b0 to the nearest vertex and b1 the furthest - Point2LL b0 = bridge[0]; - Point2LL b1 = bridge[1]; - - if (vSize2f(p0.p_ - b1) < vSize2f(p0.p_ - b0)) + const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; + if (is_scarf_closure) { - // swap vertex order - b0 = bridge[1]; - b1 = bridge[0]; + scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); + } + else + { + scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); } - distance_to_bridge_start += vSize(b0 - p0.p_); - - const double bridge_line_len = vSize(b1 - b0); - - if (bridge_line_len >= min_bridge_line_len) + if (first_split) { - // job done, we have found the first bridge line - return; + // Manually add a Z-only travel move to set the nozzle at the height of the first point + addTravel(p0.p_, always_retract, split_origin.z_); + first_split = false; } + } + + double accelerate_speed_factor = 1.0; + double accelerate_factor_destination = 1.0; + if (accelerate_remaining_distance > 0) + { + accelerate_factor_destination = 1.0 - (static_cast(accelerate_remaining_distance - length_to_process) / static_cast(accelerate_length)); - distance_to_bridge_start += bridge_line_len; + const double accelerate_factor_average = (accelerate_factor_origin + accelerate_factor_destination) / 2.0; - // finished with this segment - line_polys.removeAt(nearest); + accelerate_speed_factor = std::lerp(start_speed_ratio, 1.0, accelerate_factor_average); } + + constexpr bool travel_to_z = false; + addWallLine( + split_origin, + split_destination, + settings, + default_config, + roofing_config, + bridge_config, + flow_ratio * scarf_segment_flow_ratio, + line_width * nominal_line_width_multiplier, + non_bridge_line_volume, + accelerate_speed_factor, + distance_to_bridge_start, + travel_to_z); + + piece_remaining_distance -= length_to_process; + split_origin = split_destination; + + scarf_remaining_distance -= length_to_process; + scarf_factor_origin = scarf_factor_destination; + + accelerate_remaining_distance -= length_to_process; + accelerate_factor_origin = accelerate_factor_destination; } - else if (! bridge_wall_mask_.inside(p0.p_, true)) + + if (piece_remaining_distance > 0 && ! is_scarf_closure) { - // none of the line is over air - distance_to_bridge_start += vSize(p1.p_ - p0.p_); + const Point2LL origin = p0.p_ + normal(line_vector, piece_length * (piece + 1) - piece_remaining_distance); + addWallLine( + origin, + destination, + settings, + default_config, + roofing_config, + bridge_config, + flow_ratio, + line_width * nominal_line_width_multiplier, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start); } } - - // we have got all the way to the end of the wall without finding a bridge segment so disable coasting by setting distance_to_bridge_start back to 0 - - distance_to_bridge_start = 0; } - }; - bool first_line = true; - bool first_scarf = true; - const coord_t small_feature_max_length = settings.get("small_feature_max_length"); - const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); - Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); - const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; - small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); - const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! - const auto max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); - const auto scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); - const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; - const auto scarf_split_distance = settings.get("scarf_split_distance"); - const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); - const Ratio start_speed_ratio = smooth_speed ? settings.get("wall_0_start_speed_ratio") : 1.0_r; - const int acceleration = settings.get("wall_0_acceleration"); // mm/s² - const Velocity top_speed = default_config.getSpeed(); - const double start_speed = top_speed * start_speed_ratio; // mm/s - const coord_t accelerate_split_distance = settings.get("wall_0_speed_split_distance"); // mm - const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((std::pow(top_speed.value, 2) - std::pow(start_speed, 2)) / (2.0 * acceleration)) : 0; - - ExtrusionJunction p0 = wall[start_idx]; + p0 = p1; + } +} - const int direction = is_reversed ? -1 : 1; - const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); +coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const +{ + coord_t distance_to_bridge_start = 0; - auto addScarfedWall = [&](const bool is_scarf_closure) + if (! bridge_wall_mask_.empty()) { - coord_t scarf_remaining_distance = scarf_seam_length; - double scarf_factor_origin = 0.0; - - Point3LL split_origin = p0.p_; - if (! is_scarf_closure) - { - split_origin.z_ = scarf_max_z_offset; - } - - coord_t accelerate_remaining_distance = accelerate_length; - double accelerate_factor_origin = 0.0; - - for (size_t point_idx = 1; point_idx < max_index; point_idx++) + // there is air below the part so iterate through the lines that have not yet been output accumulating the total distance to the first bridge segment + for (unsigned point_idx = current_index; point_idx < wall.size(); ++point_idx) { - const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; + const ExtrusionJunction& p0 = wall[point_idx]; + const ExtrusionJunction& p1 = wall[(point_idx + 1) % wall.size()]; - if (! bridge_wall_mask_.empty()) + if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.p_, p1.p_)) { - computeDistanceToBridgeStart((wall.size() + start_idx + point_idx * direction - 1) % wall.size()); - } + // the line crosses the boundary between supported and non-supported regions so it will contain one or more bridge segments - if (first_line) - { - addTravel(p0.p_, always_retract); - first_line = false; - } + // determine which segments of the line are bridges - /* - If the line has variable width, break it up into pieces with the - following constraints: - - Each piece must be smaller than the Maximum Resolution setting. - - The difference between the trapezoidal shape of the line and the - rectangular shape of the line may not exceed the Maximum Extrusion - Area Deviation setting, unless required by the first constraint. - Since breaking up a line segment into N pieces (each with averaged - width) divides the area deviation by N, we can simply check how many - pieces we'd want to get low enough deviation, then check if each piece - is not too short at the end. - */ - const coord_t delta_line_width = p1.w_ - p0.w_; - const Point2LL line_vector = p1.p_ - p0.p_; - const coord_t line_length = vSize(line_vector); - /* - Calculate how much the line would deviate from the trapezoidal shape if printed at average width. - This formula is: - - Half the length times half the delta width, for the rectangular shape of the deviating side. - - Half of that because the ideal line width is trapezoidal, making the deviating part triangular. - - Double of that because the deviation occurs on both sides of the idealised line width. - This results in delta_line_width / 2 * line_length / 2 / 2 * 2 == delta_line_width * line_length / 4. - */ - const coord_t line_area_deviation = std::abs(delta_line_width) * line_length / 4; - const size_t pieces_limit_deviation = round_up_divide(line_area_deviation, max_area_deviation); // How many pieces we'd need to stay beneath the max area deviation. - const size_t pieces_limit_resolution = line_length / max_resolution; // Round down this time, to not exceed the maximum resolution. - const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. - const coord_t piece_length = round_divide(line_length, pieces); - - for (size_t piece = 0; piece < pieces; ++piece) - { - const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. - // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values - // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) - const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); - const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); - if (is_small_feature && ! is_scarf_closure) - { - constexpr bool spiralize = false; - addExtrusionMove( - destination, - default_config, - SpaceFillType::Polygons, - flow_ratio, - line_width * nominal_line_width_multiplier, - spiralize, - small_feature_speed_factor); - } - else - { - coord_t piece_remaining_distance = piece_length; + OpenLinesSet line_polys; + line_polys.addSegment(p0.p_, p1.p_); + constexpr bool restitch = false; // only a single line doesn't need stitching + line_polys = bridge_wall_mask_.intersection(line_polys, restitch); - // Cut piece into smaller parts for scarf seam - while ((scarf_remaining_distance > 0 || accelerate_remaining_distance > 0) && piece_remaining_distance > 0) + while (line_polys.size() > 0) + { + // find the bridge line segment that's nearest to p0 + size_t nearest = 0; + double smallest_dist2 = vSize2f(p0.p_ - line_polys[0][0]); + for (unsigned i = 1; i < line_polys.size(); ++i) { - std::vector split_distances{ piece_remaining_distance }; - if (scarf_remaining_distance > 0) - { - split_distances.push_back(scarf_remaining_distance); - split_distances.push_back(scarf_split_distance); - } - if (! is_scarf_closure && accelerate_remaining_distance > 0) + double dist2 = vSize2f(p0.p_ - line_polys[i][0]); + if (dist2 < smallest_dist2) { - split_distances.push_back(accelerate_remaining_distance); - split_distances.push_back(accelerate_split_distance); + nearest = i; + smallest_dist2 = dist2; } + } + const OpenPolyline& bridge = line_polys[nearest]; - coord_t length_to_process = *std::min_element(split_distances.begin(), split_distances.end()); - Point3LL split_destination = split_origin + normal(line_vector, length_to_process); + // set b0 to the nearest vertex and b1 the furthest + Point2LL b0 = bridge[0]; + Point2LL b1 = bridge[1]; - double scarf_segment_flow_ratio = 1.0; - double scarf_factor_destination = 1.0; - if (scarf_remaining_distance > 0) - { - scarf_factor_destination = 1.0 - (static_cast(scarf_remaining_distance - length_to_process) / static_cast(scarf_seam_length)); - if (! is_scarf_closure) - { - split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); - } + if (vSize2f(p0.p_ - b1) < vSize2f(p0.p_ - b0)) + { + // swap vertex order + b0 = bridge[1]; + b1 = bridge[0]; + } - const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; - double scarf_segment_flow_ratio; - if (is_scarf_closure) - { - scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); - } - else - { - scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); - } + distance_to_bridge_start += vSize(b0 - p0.p_); - if (first_scarf) - { - // Manually add a Z-only travel move to set the nozzle at the height of the first point - addTravel(p0.p_, always_retract, split_origin.z_); - first_scarf = false; - } - } + const double bridge_line_len = vSize(b1 - b0); - double acceleration_speed_factor = 1.0; - double acceleration_factor_destination = 1.0; - if (accelerate_remaining_distance > 0) - { - acceleration_factor_destination - = 1.0 - (static_cast(accelerate_remaining_distance - length_to_process) / static_cast(accelerate_length)); + if (bridge_line_len >= min_bridge_line_len) + { + // job done, we have found the first bridge line + return distance_to_bridge_start; + } - const double acceleration_factor_average = (accelerate_factor_origin + acceleration_factor_destination) / 2.0; + distance_to_bridge_start += bridge_line_len; - acceleration_speed_factor = std::lerp(start_speed_ratio, 1.0, acceleration_factor_average); - } + // finished with this segment + line_polys.removeAt(nearest); + } + } + else if (! bridge_wall_mask_.inside(p0.p_, true)) + { + // none of the line is over air + distance_to_bridge_start += vSize(p1.p_ - p0.p_); + } + } - constexpr bool travel_to_z = false; - addWallLine( - split_origin, - split_destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio * scarf_segment_flow_ratio, - line_width * nominal_line_width_multiplier, - non_bridge_line_volume, - speed_factor * acceleration_speed_factor, - distance_to_bridge_start, - travel_to_z); + // we have got all the way to the end of the wall without finding a bridge segment so disable coasting by setting distance_to_bridge_start back to 0 - piece_remaining_distance -= length_to_process; - split_origin = split_destination; + distance_to_bridge_start = 0; + } - scarf_remaining_distance -= length_to_process; - scarf_factor_origin = scarf_factor_destination; + return distance_to_bridge_start; +} - accelerate_remaining_distance -= length_to_process; - accelerate_factor_origin = acceleration_factor_destination; - } +void LayerPlan::addWall( + const ExtrusionLine& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const GCodePathConfig& roofing_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path, + const bool scarf_seam, + const bool smooth_speed) +{ + if (wall.empty()) + { + return; + } + if (is_closed) + { + // make sure wall start point is not above air! + start_idx = locateFirstSupportedVertex(wall, start_idx); + } + const bool actual_scarf_seam = scarf_seam && is_closed; - if (piece_remaining_distance > 0 && ! is_scarf_closure) - { - const Point2LL origin = p0.p_ + normal(line_vector, piece_length * (piece + 1) - piece_remaining_distance); - addWallLine( - origin, - destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio, - line_width * nominal_line_width_multiplier, - non_bridge_line_volume, - speed_factor, - distance_to_bridge_start); - } - } - } + double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output - p0 = p1; - } + const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + + const Ratio nominal_line_width_multiplier{ + 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } + }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + + const coord_t small_feature_max_length = settings.get("small_feature_max_length"); + const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); + const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; + Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); + small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); + const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! + const auto max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); + const auto scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; + const auto scarf_split_distance = settings.get("scarf_split_distance"); + const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); + const Ratio start_speed_ratio = smooth_speed ? settings.get("wall_0_start_speed_ratio") : 1.0_r; + const int acceleration = settings.get("wall_0_acceleration"); // mm/s² + const Velocity top_speed = default_config.getSpeed(); + const double start_speed = top_speed * start_speed_ratio; // mm/s + const coord_t accelerate_split_distance = settings.get("wall_0_speed_split_distance"); // mm + const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((square(top_speed.value) - square(start_speed)) / (2.0 * acceleration)) : 0; + const int direction = is_reversed ? -1 : 1; + const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); + + auto addWallSplittedPass = [&](bool is_scarf_closure) + { + addWallSplitted( + wall, + start_idx, + direction, + max_index, + settings, + default_config, + roofing_config, + bridge_config, + flow_ratio, + nominal_line_width_multiplier, + non_bridge_line_volume, + min_bridge_line_len, + always_retract, + is_small_feature, + small_feature_speed_factor, + max_area_deviation, + max_resolution, + scarf_seam_length, + scarf_seam_start_ratio, + scarf_split_distance, + scarf_max_z_offset, + start_speed_ratio, + accelerate_split_distance, + accelerate_length, + is_scarf_closure); }; - // First pass to add the wall with the scarf beginning - addScarfedWall(false); + // First pass to add the wall with the scarf beginning and acceleration + addWallSplittedPass(false); if (scarf_seam_length) { // Second pass to add the scarf closure - addScarfedWall(true); + addWallSplittedPass(true); } if (wall.size() >= 2) { if (! bridge_wall_mask_.empty()) { - computeDistanceToBridgeStart((start_idx + wall.size() - 1) % wall.size()); + computeDistanceToBridgeStart(wall, (start_idx + wall.size() - 1) % wall.size(), min_bridge_line_len); } if (wall_0_wipe_dist > 0 && ! is_linked_path) { // apply outer wall wipe - p0 = wall[start_idx]; + ExtrusionJunction p0 = wall[start_idx]; int distance_traversed = 0; for (unsigned int point_idx = 1;; point_idx++) { From cf3ece99dd31265aea85dfa51c9380d65c9850cc Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 12:02:45 +0200 Subject: [PATCH 022/139] Apply deceleration when ending an outer wall print CURA-12080 --- include/LayerPlan.h | 5 +- src/LayerPlan.cpp | 133 +++++++++++++++++++++++++------------------- 2 files changed, 80 insertions(+), 58 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 20b3023e82..2782f485be 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -839,6 +839,7 @@ class LayerPlan : public NoCopy void addWallSplitted( const ExtrusionLine& wall, + const coord_t wall_length, size_t start_idx, const int direction, const size_t max_index, @@ -859,9 +860,11 @@ class LayerPlan : public NoCopy const auto scarf_seam_start_ratio, const auto scarf_split_distance, const coord_t scarf_max_z_offset, + const coord_t speed_split_distance, const Ratio start_speed_ratio, - const coord_t accelerate_split_distance, const coord_t accelerate_length, + const Ratio end_speed_ratio, + const coord_t decelerate_length, const bool is_scarf_closure); // helper function to calculate the distance from the start of the current wall line to the first bridge segment diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 82f5361157..302152f13f 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -999,6 +999,7 @@ void LayerPlan::addWall( void LayerPlan::addWallSplitted( const ExtrusionLine& wall, + const coord_t wall_length, size_t start_idx, const int direction, const size_t max_index, @@ -1019,13 +1020,13 @@ void LayerPlan::addWallSplitted( const auto scarf_seam_start_ratio, const auto scarf_split_distance, const coord_t scarf_max_z_offset, + const coord_t speed_split_distance, const Ratio start_speed_ratio, - const coord_t accelerate_split_distance, const coord_t accelerate_length, + const Ratio end_speed_ratio, + const coord_t decelerate_length, const bool is_scarf_closure) { - constexpr double speed_factor = 1.0; - coord_t distance_to_bridge_start = 0; // will be updated before each line is processed ExtrusionJunction p0 = wall[start_idx]; bool first_line = ! is_scarf_closure; @@ -1036,11 +1037,11 @@ void LayerPlan::addWallSplitted( split_origin.z_ = scarf_max_z_offset; } - coord_t scarf_remaining_distance = scarf_seam_length; + coord_t wall_processed_distance = 0; double scarf_factor_origin = 0.0; - - coord_t accelerate_remaining_distance = accelerate_length; double accelerate_factor_origin = 0.0; + double decelerate_factor_origin = 0.0; + const coord_t start_decelerating_position = wall_length - decelerate_length; for (size_t point_idx = 1; point_idx < max_index; point_idx++) { @@ -1109,29 +1110,48 @@ void LayerPlan::addWallSplitted( { coord_t piece_remaining_distance = piece_length; - // Cut piece into smaller parts for scarf seam - while ((scarf_remaining_distance > 0 || accelerate_remaining_distance > 0) && piece_remaining_distance > 0) + // Cut piece into smaller parts for scarf seam and acceleration/deceleration + while ((! is_scarf_closure && piece_remaining_distance > 0) || (is_scarf_closure && wall_processed_distance < scarf_seam_length)) { - std::vector split_distances{ piece_remaining_distance }; - if (scarf_remaining_distance > 0) + std::vector split_positions{ wall_processed_distance + piece_remaining_distance }; + + const bool process_scarf = wall_processed_distance < scarf_seam_length; + if (process_scarf) + { + split_positions.push_back(scarf_seam_length); + split_positions.push_back(wall_processed_distance + scarf_split_distance); + } + + const bool process_acceleration = ! is_scarf_closure && wall_processed_distance < accelerate_length; + if (process_acceleration) { - split_distances.push_back(scarf_remaining_distance); - split_distances.push_back(scarf_split_distance); + split_positions.push_back(accelerate_length); + split_positions.push_back(wall_processed_distance + speed_split_distance); } - if (! is_scarf_closure && accelerate_remaining_distance > 0) + + bool deceleration_started = false; + if (! is_scarf_closure && decelerate_length > 0) { - split_distances.push_back(accelerate_remaining_distance); - split_distances.push_back(accelerate_split_distance); + deceleration_started = wall_processed_distance >= start_decelerating_position; + if (deceleration_started) + { + split_positions.push_back(wall_processed_distance + speed_split_distance); + } + else + { + split_positions.push_back(start_decelerating_position); + } } - coord_t length_to_process = *std::min_element(split_distances.begin(), split_distances.end()); + const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); + const coord_t length_to_process = destination_position - wall_processed_distance; Point3LL split_destination = split_origin + normal(line_vector, length_to_process); double scarf_segment_flow_ratio = 1.0; double scarf_factor_destination = 1.0; - if (scarf_remaining_distance > 0) + if (process_scarf) { - scarf_factor_destination = 1.0 - (static_cast(scarf_remaining_distance - length_to_process) / static_cast(scarf_seam_length)); + scarf_factor_destination = static_cast(destination_position) / static_cast(scarf_seam_length); if (! is_scarf_closure) { split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); @@ -1155,17 +1175,24 @@ void LayerPlan::addWallSplitted( } } - double accelerate_speed_factor = 1.0; + Ratio accelerate_speed_factor = 1.0_r; double accelerate_factor_destination = 1.0; - if (accelerate_remaining_distance > 0) + if (process_acceleration) { - accelerate_factor_destination = 1.0 - (static_cast(accelerate_remaining_distance - length_to_process) / static_cast(accelerate_length)); - + accelerate_factor_destination = static_cast(destination_position) / static_cast(accelerate_length); const double accelerate_factor_average = (accelerate_factor_origin + accelerate_factor_destination) / 2.0; - accelerate_speed_factor = std::lerp(start_speed_ratio, 1.0, accelerate_factor_average); } + Ratio decelerate_speed_factor = is_scarf_closure ? end_speed_ratio : 1.0_r; + double decelerate_factor_destination = 0.0; + if (deceleration_started) + { + decelerate_factor_destination = 1.0 - (static_cast(wall_length - destination_position) / static_cast(decelerate_length)); + const double decelerate_factor_average = (decelerate_factor_origin + decelerate_factor_destination) / 2.0; + decelerate_speed_factor = std::lerp(1.0, end_speed_ratio, decelerate_factor_average); + } + constexpr bool travel_to_z = false; addWallLine( split_origin, @@ -1177,35 +1204,16 @@ void LayerPlan::addWallSplitted( flow_ratio * scarf_segment_flow_ratio, line_width * nominal_line_width_multiplier, non_bridge_line_volume, - accelerate_speed_factor, + accelerate_speed_factor * decelerate_speed_factor, distance_to_bridge_start, travel_to_z); + wall_processed_distance = destination_position; piece_remaining_distance -= length_to_process; split_origin = split_destination; - - scarf_remaining_distance -= length_to_process; scarf_factor_origin = scarf_factor_destination; - - accelerate_remaining_distance -= length_to_process; accelerate_factor_origin = accelerate_factor_destination; - } - - if (piece_remaining_distance > 0 && ! is_scarf_closure) - { - const Point2LL origin = p0.p_ + normal(line_vector, piece_length * (piece + 1) - piece_remaining_distance); - addWallLine( - origin, - destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio, - line_width * nominal_line_width_multiplier, - non_bridge_line_volume, - speed_factor, - distance_to_bridge_start); + decelerate_factor_origin = decelerate_factor_destination; } } } @@ -1221,7 +1229,7 @@ coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const if (! bridge_wall_mask_.empty()) { // there is air below the part so iterate through the lines that have not yet been output accumulating the total distance to the first bridge segment - for (unsigned point_idx = current_index; point_idx < wall.size(); ++point_idx) + for (size_t point_idx = current_index; point_idx < wall.size(); ++point_idx) { const ExtrusionJunction& p0 = wall[point_idx]; const ExtrusionJunction& p1 = wall[(point_idx + 1) % wall.size()]; @@ -1330,6 +1338,7 @@ void LayerPlan::addWall( 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + const coord_t wall_length = wall.length(); const coord_t small_feature_max_length = settings.get("small_feature_max_length"); const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; @@ -1337,23 +1346,31 @@ void LayerPlan::addWall( small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! const auto max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); - const auto scarf_seam_length = std::min(wall.length(), actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const int direction = is_reversed ? -1 : 1; + const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); + + const auto scarf_seam_length = std::min(wall_length, actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; const auto scarf_split_distance = settings.get("scarf_split_distance"); const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); + + const Velocity top_speed = default_config.getSpeed(); + const coord_t speed_split_distance = settings.get("wall_0_speed_split_distance"); // mm const Ratio start_speed_ratio = smooth_speed ? settings.get("wall_0_start_speed_ratio") : 1.0_r; const int acceleration = settings.get("wall_0_acceleration"); // mm/s² - const Velocity top_speed = default_config.getSpeed(); - const double start_speed = top_speed * start_speed_ratio; // mm/s - const coord_t accelerate_split_distance = settings.get("wall_0_speed_split_distance"); // mm - const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((square(top_speed.value) - square(start_speed)) / (2.0 * acceleration)) : 0; - const int direction = is_reversed ? -1 : 1; - const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); + const Velocity start_speed = top_speed * start_speed_ratio; // mm/s + const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(start_speed)) / (2.0 * acceleration)) : 0; // µm + + const Ratio end_speed_ratio = smooth_speed ? settings.get("wall_0_end_speed_ratio") : 1.0_r; + const int deceleration = settings.get("wall_0_deceleration"); // mm/s² + const Velocity end_speed = top_speed * end_speed_ratio; // mm/s + const coord_t decelerate_length = (smooth_speed && end_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(end_speed)) / (2.0 * deceleration)) : 0; // µm auto addWallSplittedPass = [&](bool is_scarf_closure) { addWallSplitted( wall, + wall_length, start_idx, direction, max_index, @@ -1374,16 +1391,18 @@ void LayerPlan::addWall( scarf_seam_start_ratio, scarf_split_distance, scarf_max_z_offset, + speed_split_distance, start_speed_ratio, - accelerate_split_distance, accelerate_length, + end_speed_ratio, + decelerate_length, is_scarf_closure); }; // First pass to add the wall with the scarf beginning and acceleration addWallSplittedPass(false); - if (scarf_seam_length) + if (scarf_seam_length > 0) { // Second pass to add the scarf closure addWallSplittedPass(true); @@ -1399,7 +1418,7 @@ void LayerPlan::addWall( if (wall_0_wipe_dist > 0 && ! is_linked_path) { // apply outer wall wipe ExtrusionJunction p0 = wall[start_idx]; - int distance_traversed = 0; + coord_t distance_traversed = 0; for (unsigned int point_idx = 1;; point_idx++) { if (point_idx > wall.size() && distance_traversed == 0) // Wall has a total circumference of 0. This loop would never end. @@ -1407,7 +1426,7 @@ void LayerPlan::addWall( break; // No wipe if the wall has no circumference. } ExtrusionJunction p1 = wall[(start_idx + point_idx) % wall.size()]; - int p0p1_dist = vSize(p1 - p0); + coord_t p0p1_dist = vSize(p1 - p0); if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) { Point2LL vector = p1.p_ - p0.p_; From 6fc32c1466ebc3dbe1ce251de107c66d9f51aa6e Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 12:49:49 +0200 Subject: [PATCH 023/139] Add code documentation CURA-12080 --- include/LayerPlan.h | 66 +++++++++++++++++++++++++++++++++++++++++++-- src/LayerPlan.cpp | 48 ++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 2782f485be..00e6126660 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -824,10 +824,34 @@ class LayerPlan : public NoCopy const Ratio flow_ratio, const double fan_speed); + /*! + * @brief Send a GCodePath line to the communication object, applying proper Z offsets + * @param path The path to be sent + * @param position The start position (which is not included in the path points) + * @param extrude_speed The actual used extrusion speed + */ void sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed); + /*! + * @brief Write a travel move and properly apply the various Z offsets + * @param gcode The actual GCode exporter + * @param position The position to move to. The Z coordinate is an offset to the current layer position + * @param speed The actual used speed + * @param path_z_offset The global path Z offset to be applied + * @note This function is to be used when dealing with 3D coordinates. If you have 2D coordinates, just call gcode.writeTravel() + */ void writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset); + /*! + * \brief Write an extrusion move and properly apply the various Z offsets + * \param gcode The actual GCode exporter + * \param position The position to move to. The Z coordinate is an offset to the current layer position + * \param speed The actual used speed + * \param path_z_offset The global path Z offset to be applied + * \param extrusion_mm3_per_mm The desired flow rate + * \param feature The current feature being printed + * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate + */ void writeExtrusionRelativeZ( GCodeExport& gcode, const Point3LL& position, @@ -837,6 +861,38 @@ class LayerPlan : public NoCopy PrintFeatureType feature, bool update_extrusion_offset = false); + /*! + * \brief Add a wall to the gcode with optimized order + * \param wall The full wall to be added + * \param wall_length The pre-calculated full wall length + * \param start_idx The index of the point where to start printing the wall + * \param direction The direction along which to print the wall, which should be 1 or -1 + * \param max_index The last index to be used when iterating over the wall segments + * \param settings The settings which should apply to this wall added to the layer plan + * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air + * \param roofing_config The config with which to print the wall lines that are exposed to air + * \param bridge_config The config with which to print the wall lines that are spanning a bridge + * \param flow_ratio The ratio with which to multiply the extrusion amount + * \param line_width_ratio The line width ratio to be applied + * \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 lin + * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move + * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param is_small_feature Indicates whether the wall is so small that it should be processed differently + * \param small_feature_speed_factor The speed factor to be applied to small feature walls + * \param max_area_deviation The maximum allowed area deviation to split a segment into pieces + * \param max_resolution The maximum resolution to split a segment into pieces + * \param scarf_seam_length The length of the scarf joint seam, which may be 0 if there is none + * \param scarf_seam_start_ratio The ratio of the line thickness to start the scarf seam with + * \param scarf_split_distance The maximum length of a segment to apply the scarf seam gradient, longer segments will be splitted + * \param scarf_max_z_offset The maximum Z offset te be applied at the lowest position of the scarf seam + * \param speed_split_distance The maximum length of a segment to apply the acceleration/deceleration gradient, longer segments will be splitted + * \param start_speed_ratio The ratio of the top speed to be applied when starting the segment, then accelerate gradually to full speed + * \param accelerate_length The pre-calculated length of the acceleration phase + * \param end_speed_ratio The ratio of the top speed to be applied when finishing a segment + * \param decelerate_length The pre-calculated length of the deceleration phase + * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or + * the normal first pass of the wall + */ void addWallSplitted( const ExtrusionLine& wall, const coord_t wall_length, @@ -848,7 +904,7 @@ class LayerPlan : public NoCopy const GCodePathConfig& roofing_config, const GCodePathConfig& bridge_config, const double flow_ratio, - const Ratio nominal_line_width_multiplier, + const Ratio line_width_ratio, double& non_bridge_line_volume, const coord_t min_bridge_line_len, const bool always_retract, @@ -867,7 +923,13 @@ class LayerPlan : public NoCopy const coord_t decelerate_length, const bool is_scarf_closure); - // helper function to calculate the distance from the start of the current wall line to the first bridge segment + /*! + * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment + * \param wall The currently processed wall + * \param current_index The index of the currently processed point + * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move + * \return The distance from the start of the current wall line to the first bridge segment + */ coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const; }; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 302152f13f..646cb3f0fa 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1008,7 +1008,7 @@ void LayerPlan::addWallSplitted( const GCodePathConfig& roofing_config, const GCodePathConfig& bridge_config, const double flow_ratio, - const Ratio nominal_line_width_multiplier, + const Ratio line_width_ratio, double& non_bridge_line_volume, const coord_t min_bridge_line_len, const bool always_retract, @@ -1037,11 +1037,11 @@ void LayerPlan::addWallSplitted( split_origin.z_ = scarf_max_z_offset; } - coord_t wall_processed_distance = 0; - double scarf_factor_origin = 0.0; - double accelerate_factor_origin = 0.0; - double decelerate_factor_origin = 0.0; - const coord_t start_decelerating_position = wall_length - decelerate_length; + coord_t wall_processed_distance = 0; // This will grow while we travel along the wall, to the total wall length + double scarf_factor_origin = 0.0; // Interpolation factor at the current point for the scarf + double accelerate_factor_origin = 0.0; // Interpolation factor at the current point for the acceleration + double decelerate_factor_origin = 0.0; // Interpolation factor at the current point for the deceleration + const coord_t start_decelerate_position = wall_length - decelerate_length; for (size_t point_idx = 1; point_idx < max_index; point_idx++) { @@ -1097,14 +1097,7 @@ void LayerPlan::addWallSplitted( if (is_small_feature && ! is_scarf_closure) { constexpr bool spiralize = false; - addExtrusionMove( - destination, - default_config, - SpaceFillType::Polygons, - flow_ratio, - line_width * nominal_line_width_multiplier, - spiralize, - small_feature_speed_factor); + addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width * line_width_ratio, spiralize, small_feature_speed_factor); } else { @@ -1113,6 +1106,8 @@ void LayerPlan::addWallSplitted( // Cut piece into smaller parts for scarf seam and acceleration/deceleration while ((! is_scarf_closure && piece_remaining_distance > 0) || (is_scarf_closure && wall_processed_distance < scarf_seam_length)) { + // Make a list of all the possible incoming positions where we would eventually want to stop next + // The positions are expressed in distance from wall start along the wall segments std::vector split_positions{ wall_processed_distance + piece_remaining_distance }; const bool process_scarf = wall_processed_distance < scarf_seam_length; @@ -1132,31 +1127,37 @@ void LayerPlan::addWallSplitted( bool deceleration_started = false; if (! is_scarf_closure && decelerate_length > 0) { - deceleration_started = wall_processed_distance >= start_decelerating_position; + deceleration_started = wall_processed_distance >= start_decelerate_position; if (deceleration_started) { split_positions.push_back(wall_processed_distance + speed_split_distance); } else { - split_positions.push_back(start_decelerating_position); + split_positions.push_back(start_decelerate_position); } } + // Now take the closest position candidate and make a sub-segment to it const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); const coord_t length_to_process = destination_position - wall_processed_distance; Point3LL split_destination = split_origin + normal(line_vector, length_to_process); double scarf_segment_flow_ratio = 1.0; - double scarf_factor_destination = 1.0; + double scarf_factor_destination = 1.0; // Out of range, scarf is done => 1.0 if (process_scarf) { + // Calculate scarf interpolation factor on the destination point scarf_factor_destination = static_cast(destination_position) / static_cast(scarf_seam_length); + + // Interpolate Z offset according to interpolation factor if (! is_scarf_closure) { split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); } + // Interpolate flow according to interpolation factor average, because it can't be different + // at start and end positions const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; if (is_scarf_closure) { @@ -1176,23 +1177,28 @@ void LayerPlan::addWallSplitted( } Ratio accelerate_speed_factor = 1.0_r; - double accelerate_factor_destination = 1.0; + double accelerate_factor_destination = 1.0; // Out of range, acceleration is done => 1.0 if (process_acceleration) { + // Interpolate speed according to interpolation factor average, because it can't be different + // at start and end positions accelerate_factor_destination = static_cast(destination_position) / static_cast(accelerate_length); const double accelerate_factor_average = (accelerate_factor_origin + accelerate_factor_destination) / 2.0; accelerate_speed_factor = std::lerp(start_speed_ratio, 1.0, accelerate_factor_average); } Ratio decelerate_speed_factor = is_scarf_closure ? end_speed_ratio : 1.0_r; - double decelerate_factor_destination = 0.0; + double decelerate_factor_destination = 0.0; // Out of range, deceleration is not started => 0.0 if (deceleration_started) { + // Interpolate speed according to interpolation factor average, because it can't be different + // at start and end positions decelerate_factor_destination = 1.0 - (static_cast(wall_length - destination_position) / static_cast(decelerate_length)); const double decelerate_factor_average = (decelerate_factor_origin + decelerate_factor_destination) / 2.0; decelerate_speed_factor = std::lerp(1.0, end_speed_ratio, decelerate_factor_average); } + // now add the (sub-)segment constexpr bool travel_to_z = false; addWallLine( split_origin, @@ -1202,7 +1208,7 @@ void LayerPlan::addWallSplitted( roofing_config, bridge_config, flow_ratio * scarf_segment_flow_ratio, - line_width * nominal_line_width_multiplier, + line_width * line_width_ratio, non_bridge_line_volume, accelerate_speed_factor * decelerate_speed_factor, distance_to_bridge_start, @@ -1340,7 +1346,7 @@ void LayerPlan::addWall( const coord_t wall_length = wall.length(); const coord_t small_feature_max_length = settings.get("small_feature_max_length"); - const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); + const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall_length < small_feature_max_length; const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); From e184deb6a98e4958439d46fcdaff93d804538a9f Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 13:59:36 +0200 Subject: [PATCH 024/139] Fix edge cases with small models CURA-12081 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 646cb3f0fa..eb08b2b234 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1032,7 +1032,7 @@ void LayerPlan::addWallSplitted( bool first_line = ! is_scarf_closure; bool first_split = ! is_scarf_closure; Point3LL split_origin = p0.p_; - if (! is_scarf_closure) + if (! is_scarf_closure && scarf_seam_length > 0) { split_origin.z_ = scarf_max_z_offset; } @@ -1104,7 +1104,7 @@ void LayerPlan::addWallSplitted( coord_t piece_remaining_distance = piece_length; // Cut piece into smaller parts for scarf seam and acceleration/deceleration - while ((! is_scarf_closure && piece_remaining_distance > 0) || (is_scarf_closure && wall_processed_distance < scarf_seam_length)) + while (piece_remaining_distance > 0 && (! is_scarf_closure || wall_processed_distance < scarf_seam_length)) { // Make a list of all the possible incoming positions where we would eventually want to stop next // The positions are expressed in distance from wall start along the wall segments From 19fa63cc9dec0154103e4a2d34323d8e6a015c31 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 14:47:19 +0200 Subject: [PATCH 025/139] Fix GCode display issue CURA-12081 --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index eb08b2b234..029fb038c5 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2238,7 +2238,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) { Communication* communication = Application::getInstance().communication_; communication->setLayerForSend(layer_nr_); - communication->sendCurrentPosition(gcode.getPositionXY()); + communication->sendCurrentPosition(gcode.getPosition()); gcode.setLayerNr(layer_nr_); gcode.writeLayerComment(layer_nr_); From 3428bf807d76bc2f6305ae4856a4f27456ec515d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 15:28:36 +0200 Subject: [PATCH 026/139] Fix MacOS build CURA-12081 --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 8ccc943895..ee853ca551 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3126,7 +3126,7 @@ bool FffGcodeWriter::processInsets( mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); - constexpr Shape disallowed_areas_for_seams; + const Shape disallowed_areas_for_seams; constexpr bool scarf_seam = true; constexpr bool smooth_speed = true; InsetOrderOptimizer wall_orderer( From 008ff2ae18bed9b3b0dbb1a86c6fcf84120613b6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 23 Aug 2024 16:51:59 +0200 Subject: [PATCH 027/139] Fix MacOS build CURA-12081 --- include/geometry/Point2LL.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/geometry/Point2LL.h b/include/geometry/Point2LL.h index 91b26c6ab6..424ba212d2 100644 --- a/include/geometry/Point2LL.h +++ b/include/geometry/Point2LL.h @@ -10,8 +10,8 @@ Integer points are used to avoid floating point rounding errors, and because Cli */ #define INLINE static inline +#include #include -#include #include #include From 6159a2816c3007f63b62f830aa8296e99621e1a3 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:54:36 +0200 Subject: [PATCH 028/139] Fix for issue https://github.com/Ultimaker/Cura/issues/19586 --- src/support.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/support.cpp b/src/support.cpp index 17a55d855a..898b8b2835 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -1712,7 +1712,8 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh { return; } - const coord_t z_distance_top = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); // Number of layers between support roof and model. + const coord_t support_top_distance = mesh.settings.get("support_top_distance"); + const coord_t z_distance_top = round_up_divide(support_top_distance, layer_height); // Number of layers between support roof and model. const coord_t roof_line_width = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_line_width"); const coord_t roof_outline_offset = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_offset"); @@ -1732,7 +1733,7 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh Shape roofs; generateSupportInterfaceLayer(global_support_areas_per_layer[layer_idx], mesh_outlines, roof_line_width, roof_outline_offset, minimum_roof_area, roofs); support_layers[layer_idx].support_roof.push_back(roofs); - if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + if (layer_idx > 0 && layer_idx < support_layers.size() - 1 && support_top_distance % layer_height != 0) { support_layers[layer_idx].support_fractional_roof.push_back(roofs.difference(support_layers[layer_idx + 1].support_roof)); } From 7056d2ca336fbdb5f08da9b314167693f7cbe12c Mon Sep 17 00:00:00 2001 From: HellAholic Date: Wed, 28 Aug 2024 13:16:09 +0000 Subject: [PATCH 029/139] Set conan package version 5.8.1 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index c3ba739591..103c698ff9 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.8.0" +version: "5.8.1" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: @@ -6,4 +6,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/5.8.0" + - "cura_resources/5.8.1" From 969c312b22ce9f62cad8831035c810a4ef787da4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 24 Sep 2024 20:16:56 +0200 Subject: [PATCH 030/139] Remove dead code and fix tests. Most of the tests where going wrong because the z-height introduced for the scarf seam introduced a refactor that used a variable before it had the correct assignment. part of CURA-12081 --- src/FffPolygonGenerator.cpp | 9 --------- src/gcodeExport.cpp | 2 +- tests/FffGcodeWriterTest.cpp | 4 ++-- tests/GCodeExportTest.cpp | 2 +- tests/arcus/MockCommunication.h | 6 ++---- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 0a0351e460..68db1cf1c1 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -213,15 +213,6 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe slicerList.push_back(slicer); - /* - for(SlicerLayer& layer : slicer->layers) - { - //Reporting the outline here slows down the engine quite a bit, so only do so when debugging. - sendPolygons("outline", layer_nr, layer.z, layer.polygonList); - sendPolygons("openoutline", layer_nr, layer.openPolygonList); - } - */ - Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size()); } diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 938980e456..e94c988b78 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1248,8 +1248,8 @@ void GCodeExport::writeZhopStart(const coord_t hop_height, Velocity speed /*= 0* const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[current_extruder_]; speed = extruder.settings_.get("speed_z_hop"); } - const coord_t target_z = current_layer_z_ + is_z_hopped_; is_z_hopped_ = hop_height; + const coord_t target_z = current_layer_z_ + is_z_hopped_; current_speed_ = speed; *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ target_z } << new_line_; Application::getInstance().communication_->sendLineTo(PrintFeatureType::MoveRetraction, Point3LL(current_position_.x_, current_position_.y_, target_z), 0, 0, speed); diff --git a/tests/FffGcodeWriterTest.cpp b/tests/FffGcodeWriterTest.cpp index a3c52d6509..452dd4a749 100644 --- a/tests/FffGcodeWriterTest.cpp +++ b/tests/FffGcodeWriterTest.cpp @@ -155,12 +155,12 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) Point2LL last; for (const auto& path:gcode_layer.extruder_plans_[0].paths_) { for (const auto& point: path.points) { - Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, point, last); + Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, point.toPoint2LL(), last); int64_t dist = vSize2(p - closest_here); if (dist Date: Tue, 24 Sep 2024 23:06:34 +0200 Subject: [PATCH 031/139] The 'spiralize' variable was missing. In the previous spike, the 'travel_to_z' argument was added (at the end), causing the need for previously omitted (default) parameters to be added as well. In this case, 'spiralize' was omitted, causing the FAN_SPEED_DEFAULT (which is an enum set to -1) to be interpreted as a speed_factor instead. part of CURA-12081 --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 35b18b5ed9..a3e507a5f6 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -925,7 +925,7 @@ void LayerPlan::addWallLine( if (bridge_line_len > min_line_len) { - addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); + addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); non_bridge_line_volume = 0; cur_point = b1; // after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag From 348929ed06312660e6c136113b48987da423914f Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 25 Sep 2024 15:23:02 +0200 Subject: [PATCH 032/139] Rename 'Splitted' to 'Split' part of CURA-12081 --- include/LayerPlan.h | 2 +- src/LayerPlan.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 111d827193..40866afb37 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -894,7 +894,7 @@ class LayerPlan : public NoCopy * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or * the normal first pass of the wall */ - void addWallSplitted( + void addSplitWall( const ExtrusionLine& wall, const coord_t wall_length, size_t start_idx, diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index a3e507a5f6..b4ad5d1097 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -997,7 +997,7 @@ void LayerPlan::addWall( addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } -void LayerPlan::addWallSplitted( +void LayerPlan::addSplitWall( const ExtrusionLine& wall, const coord_t wall_length, size_t start_idx, @@ -1372,9 +1372,9 @@ void LayerPlan::addWall( const Velocity end_speed = top_speed * end_speed_ratio; // mm/s const coord_t decelerate_length = (smooth_speed && end_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(end_speed)) / (2.0 * deceleration)) : 0; // µm - auto addWallSplittedPass = [&](bool is_scarf_closure) + auto addSplitWallPass = [&](bool is_scarf_closure) { - addWallSplitted( + addSplitWall( wall, wall_length, start_idx, @@ -1406,12 +1406,12 @@ void LayerPlan::addWall( }; // First pass to add the wall with the scarf beginning and acceleration - addWallSplittedPass(false); + addSplitWallPass(false); if (scarf_seam_length > 0) { // Second pass to add the scarf closure - addWallSplittedPass(true); + addSplitWallPass(true); } if (wall.size() >= 2) @@ -2233,7 +2233,6 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) last_extruder_plan.processFanSpeedForMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); } - void LayerPlan::writeGCode(GCodeExport& gcode) { auto communication = Application::getInstance().communication_; From e7ab2d1538bb22857e1f59b0ec5862a8946900f9 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 25 Sep 2024 15:28:04 +0200 Subject: [PATCH 033/139] Fix coasting for 'split-up' paths. The coasting functionality previously relied (partially) on paths being their own 'complete' extrusions, in between wich where (presumably, mostly) travels. So it only coasted on the last path before a travel, and didn't take into account paths that where to short to complete the full coast (it didn't continue on to the next). This commit fixes that partially -- the volume already coasted isn't taken into account yet, but otherwise it should work as before, but then with the split paths the scarf and accellleration start/end introduced. part of CURA-12081 --- include/LayerPlan.h | 13 ++++++++++- src/LayerPlan.cpp | 55 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 40866afb37..4550a80b9f 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -57,6 +57,14 @@ class LayerPlan : public NoCopy #endif public: + // 'AdjustCoasting'; because split-up paths from the same extruder (with no travel moves between them) should count as the same path w.r.t. coasting. + enum class AdjustCoasting + { + AsNormal, + CoastEntirePath, + ContinueCoasting + }; + const PathConfigStorage configs_storage_; //!< The line configs for this layer for each feature type const coord_t z_; coord_t final_travel_z_; @@ -740,6 +748,8 @@ class LayerPlan : public NoCopy * \param path_idx The index into LayerPlan::paths for the next path to be * written to GCode. * \param layer_thickness The height of the current layer. + * \param insertTempOnTime A function that inserts temperature changes at a given time. + * \param coasting_adjust Paths can be split up, so we need to know when to continue coasting from last, or even coast the entire path. * \return Whether any GCode has been written for the path. */ bool writePathWithCoasting( @@ -747,7 +757,8 @@ class LayerPlan : public NoCopy const size_t extruder_plan_idx, const size_t path_idx, const coord_t layer_thickness, - const std::function insertTempOnTime); + const std::function insertTempOnTime, + const AdjustCoasting coasting_adjust); /*! * Applying speed corrections for minimal layer times and determine the fanSpeed. diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b4ad5d1097..69c4021ee0 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -32,6 +32,8 @@ #include "utils/polygonUtils.h" #include "utils/section_type.h" +#include "range/v3/view/chunk_by.hpp" + namespace cura { @@ -2365,6 +2367,45 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); }; + const double coasting_volume = extruder.settings_.get("coasting_volume"); + std::vector coasting_adjust_per_path(paths.size(), AdjustCoasting::AsNormal); + if (coasting_volume > 0) + { + // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). + // Note that this doesn't perform the coasting itself, it just calculates the 'adjust coasting' vector needed by the 'writePathWithCoasting' func. + // All of this is nescesary since we split up paths because of scarf and accelleration-adjustments (start/end), so we need to have adjacency info. + for (const auto& reversed_chunk : + paths | + ranges::views::enumerate | + ranges::views::reverse | + ranges::views::chunk_by([](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) + { + double coasting_left = coasting_volume; + for (const auto& [path_idx, path] : reversed_chunk) + { + if (path.isTravelPath()) + { + break; + } + + coord_t accumulated_length = 0; + for (size_t i_pt = 1; i_pt < path.points.size(); i_pt++) + { + accumulated_length += (path.points[i_pt - 1] - path.points[i_pt]).vSize(); + } + const double path_volume = INT2MM2(INT2MM(accumulated_length * path.config.getLineWidth()) * layer_thickness_); + + coasting_adjust_per_path[path_idx] = path_volume < coasting_left ? AdjustCoasting::CoastEntirePath : AdjustCoasting::ContinueCoasting; + + coasting_left -= path_volume; + if (coasting_left <= 0) + { + break; + } + } + } + } + for (int64_t path_idx = 0; path_idx < paths.size(); path_idx++) { extruder_plan.handleInserts(path_idx, gcode); @@ -2555,7 +2596,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool coasting = extruder.settings_.get("coasting_enable"); if (coasting) { - coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness_, insertTempOnTime); + coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness_, insertTempOnTime, coasting_adjust_per_path[path_idx]); } if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm @@ -2713,7 +2754,8 @@ bool LayerPlan::writePathWithCoasting( const size_t extruder_plan_idx, const size_t path_idx, const coord_t layer_thickness, - const std::function insertTempOnTime) + const std::function insertTempOnTime, + const AdjustCoasting coasting_adjust) { ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; @@ -2724,7 +2766,7 @@ bool LayerPlan::writePathWithCoasting( } const std::vector& paths = extruder_plan.paths_; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! paths[path_idx + 1].config.isTravelPath()) || path.points.size() < 2) + if (path_idx + 1 >= paths.size() || (path.isTravelPath() || !(paths[path_idx + 1].config.isTravelPath() || coasting_adjust == AdjustCoasting::ContinueCoasting)) || path.points.size() < 2) { return false; } @@ -2733,8 +2775,10 @@ bool LayerPlan::writePathWithCoasting( const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; - const coord_t coasting_dist - = MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues + coord_t coasting_dist + = coasting_adjust == AdjustCoasting::CoastEntirePath + ? std::numeric_limits::max() / 2 + : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); const coord_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues @@ -2771,6 +2815,7 @@ bool LayerPlan::writePathWithCoasting( last = &point; } + coasting_dist = std::min(coasting_dist, accumulated_dist); // if the path is shorter than coasting_dist, we should coast the whole path if (accumulated_dist < coasting_min_dist_considered) { From 5608352b2dae51a23fa27b138e5ccfaf8fc94e20 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 25 Sep 2024 16:07:48 +0200 Subject: [PATCH 034/139] Coast less if the next ('split') path(s) are already 'coasted away'. If, due to the compensation for the paths being split (because of scarf-seams or introduced start/end acceleration-changes), the next path(s) in the 'chunk' are already going to be comletely gone due to coasting, coast less than the 'full' volume for the remainder. part of CURA-12081 --- include/LayerPlan.h | 2 +- src/LayerPlan.cpp | 32 +++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 4550a80b9f..a2ab689f3b 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -758,7 +758,7 @@ class LayerPlan : public NoCopy const size_t path_idx, const coord_t layer_thickness, const std::function insertTempOnTime, - const AdjustCoasting coasting_adjust); + const std::pair coasting_adjust); /*! * Applying speed corrections for minimal layer times and determine the fanSpeed. diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 69c4021ee0..b07bf03fd2 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2367,18 +2367,25 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); }; - const double coasting_volume = extruder.settings_.get("coasting_volume"); - std::vector coasting_adjust_per_path(paths.size(), AdjustCoasting::AsNormal); - if (coasting_volume > 0) + std::vector> coasting_adjust_per_path; + if (extruder.settings_.get("coasting_enable")) { // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). // Note that this doesn't perform the coasting itself, it just calculates the 'adjust coasting' vector needed by the 'writePathWithCoasting' func. // All of this is nescesary since we split up paths because of scarf and accelleration-adjustments (start/end), so we need to have adjacency info. + + const double coasting_volume = extruder.settings_.get("coasting_volume"); + coasting_adjust_per_path.assign(paths.size(), { AdjustCoasting::AsNormal, coasting_volume }); + for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | - ranges::views::chunk_by([](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) + ranges::views::chunk_by( + [](const auto& path_a, const auto& path_b) + { + return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); + })) { double coasting_left = coasting_volume; for (const auto& [path_idx, path] : reversed_chunk) @@ -2395,7 +2402,14 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } const double path_volume = INT2MM2(INT2MM(accumulated_length * path.config.getLineWidth()) * layer_thickness_); - coasting_adjust_per_path[path_idx] = path_volume < coasting_left ? AdjustCoasting::CoastEntirePath : AdjustCoasting::ContinueCoasting; + if (path_volume < coasting_left) + { + coasting_adjust_per_path[path_idx].first = AdjustCoasting::CoastEntirePath; + } + else + { + coasting_adjust_per_path[path_idx] = { AdjustCoasting::ContinueCoasting, coasting_left }; + } coasting_left -= path_volume; if (coasting_left <= 0) @@ -2755,18 +2769,18 @@ bool LayerPlan::writePathWithCoasting( const size_t path_idx, const coord_t layer_thickness, const std::function insertTempOnTime, - const AdjustCoasting coasting_adjust) + const std::pair coasting_adjust) { ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - const double coasting_volume = extruder.settings_.get("coasting_volume"); + const double coasting_volume = std::min(extruder.settings_.get("coasting_volume"), coasting_adjust.second); if (coasting_volume <= 0) { return false; } const std::vector& paths = extruder_plan.paths_; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || !(paths[path_idx + 1].config.isTravelPath() || coasting_adjust == AdjustCoasting::ContinueCoasting)) || path.points.size() < 2) + if (path_idx + 1 >= paths.size() || (path.isTravelPath() || !(paths[path_idx + 1].config.isTravelPath() || coasting_adjust.first == AdjustCoasting::ContinueCoasting)) || path.points.size() < 2) { return false; } @@ -2776,7 +2790,7 @@ bool LayerPlan::writePathWithCoasting( const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; coord_t coasting_dist - = coasting_adjust == AdjustCoasting::CoastEntirePath + = coasting_adjust.first == AdjustCoasting::CoastEntirePath ? std::numeric_limits::max() / 2 : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); From 5e365b066bdfd0f14f6cf6b2151486d3bb676e87 Mon Sep 17 00:00:00 2001 From: rburema Date: Wed, 25 Sep 2024 14:08:42 +0000 Subject: [PATCH 035/139] Applied clang-format. --- src/LayerPlan.cpp | 28 ++++++++++++---------------- src/plugins/converters.cpp | 5 +++-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b07bf03fd2..4f09f02738 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -24,6 +24,7 @@ #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" #include "raft.h" // getTotalExtraLayers +#include "range/v3/view/chunk_by.hpp" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "utils/Simplify.h" @@ -32,8 +33,6 @@ #include "utils/polygonUtils.h" #include "utils/section_type.h" -#include "range/v3/view/chunk_by.hpp" - namespace cura { @@ -2377,15 +2376,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const double coasting_volume = extruder.settings_.get("coasting_volume"); coasting_adjust_per_path.assign(paths.size(), { AdjustCoasting::AsNormal, coasting_volume }); - for (const auto& reversed_chunk : - paths | - ranges::views::enumerate | - ranges::views::reverse | - ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) - { - return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); - })) + for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse + | ranges::views::chunk_by( + [](const auto&path_a, const auto&path_b) + { + return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); + })) { double coasting_left = coasting_volume; for (const auto& [path_idx, path] : reversed_chunk) @@ -2780,7 +2776,8 @@ bool LayerPlan::writePathWithCoasting( } const std::vector& paths = extruder_plan.paths_; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || !(paths[path_idx + 1].config.isTravelPath() || coasting_adjust.first == AdjustCoasting::ContinueCoasting)) || path.points.size() < 2) + if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! (paths[path_idx + 1].config.isTravelPath() || coasting_adjust.first == AdjustCoasting::ContinueCoasting)) + || path.points.size() < 2) { return false; } @@ -2789,10 +2786,9 @@ bool LayerPlan::writePathWithCoasting( const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; - coord_t coasting_dist - = coasting_adjust.first == AdjustCoasting::CoastEntirePath - ? std::numeric_limits::max() / 2 - : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues + coord_t coasting_dist = coasting_adjust.first == AdjustCoasting::CoastEntirePath + ? std::numeric_limits::max() / 2 + : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); const coord_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp index f1bd4dad79..f9dfcfed90 100644 --- a/src/plugins/converters.cpp +++ b/src/plugins/converters.cpp @@ -168,7 +168,8 @@ postprocess_response::native_value_type } infill_generate_request::value_type - infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) const + infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) + const { value_type message{}; message.set_pattern(pattern); @@ -181,7 +182,7 @@ infill_generate_request::value_type // ------------------------------------------------------------ // Add current z height to settings message // ------------------------------------------------------------ - msg_settings->insert({ "z" , std::to_string(z) }); + msg_settings->insert({ "z", std::to_string(z) }); if (inner_contour.empty()) { From 8c46f31847b10d70f07a5e8b1c6e224479fc687b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 25 Sep 2024 16:32:29 +0200 Subject: [PATCH 036/139] Disable scarf for initial layer (protects the buildplate). part of CURA-12081 --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 4f09f02738..b28cc5034c 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1394,7 +1394,7 @@ void LayerPlan::addWall( small_feature_speed_factor, max_area_deviation, max_resolution, - scarf_seam_length, + layer_nr_ > 0 ? scarf_seam_length : 0, scarf_seam_start_ratio, scarf_split_distance, scarf_max_z_offset, From d5b220bef0b1cb2e8ae6ea4322585973d22097d7 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Mon, 30 Sep 2024 08:37:02 +0000 Subject: [PATCH 037/139] Applied clang-format. --- src/LayerPlan.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index fd0f683f7d..271d29ff9a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2097,7 +2097,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } } // Fan speed may already be set by a plugin. Prevents two fan speed commands without move in between. - if(!extruder_plan.paths_.empty() && extruder_plan.paths_.front().fan_speed == -1) + if (! extruder_plan.paths_.empty() && extruder_plan.paths_.front().fan_speed == -1) { gcode.writePrepareFansForExtrusion(extruder_plan.getFanSpeed()); } @@ -2124,7 +2124,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& path = paths[path_idx]; // If travel paths have a non default fan speed for some reason set it as fan speed as such modification could be made by a plugin. - if(!path.isTravelPath() || path.fan_speed >= 0) + if (! path.isTravelPath() || path.fan_speed >= 0) { const double path_fan_speed = path.getFanSpeed(); gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); @@ -2302,7 +2302,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool spiralize = path.spiralize; if (! spiralize) // normal (extrusion) move (with coasting) { - bool coasting = extruder.settings_.get("coasting_enable"); if (coasting) { From f7171956eab507932c3cbd46eab965f9f17b5472 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 30 Sep 2024 14:02:58 +0200 Subject: [PATCH 038/139] Use uptodate arcus --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index f5da580fcb..909c921748 100644 --- a/conandata.yml +++ b/conandata.yml @@ -2,7 +2,7 @@ version: "5.9.0-alpha.0" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: - - "arcus/5.3.1" + - "arcus/5.4.1" requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: From 5303bfc5fe9aed2a3e84b3d7eaadfecf44cc97f6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 30 Sep 2024 16:07:35 +0200 Subject: [PATCH 039/139] Fix unit tests after arcus change --- tests/arcus/MockSocket.cpp | 5 +++-- tests/arcus/MockSocket.h | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/arcus/MockSocket.cpp b/tests/arcus/MockSocket.cpp index df41690a41..a6860202c4 100644 --- a/tests/arcus/MockSocket.cpp +++ b/tests/arcus/MockSocket.cpp @@ -21,9 +21,10 @@ void MockSocket::reset() { /* Do nothing. */ } -void MockSocket::sendMessage(Arcus::MessagePtr message) +bool MockSocket::sendMessage(Arcus::MessagePtr message) { sent_messages.push_back(message); + return true; } Arcus::MessagePtr MockSocket::takeNextMessage() @@ -45,4 +46,4 @@ Arcus::MessagePtr MockSocket::popMessageFromSendQueue() return result; } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/tests/arcus/MockSocket.h b/tests/arcus/MockSocket.h index 272720f216..9bed7467e0 100644 --- a/tests/arcus/MockSocket.h +++ b/tests/arcus/MockSocket.h @@ -4,9 +4,10 @@ #ifndef MOCKSOCKET_H #define MOCKSOCKET_H -#include //Inheriting from this to be able to swap this socket in the tested class. #include //History of sent and received messages. +#include //Inheriting from this to be able to swap this socket in the tested class. + namespace cura { @@ -27,7 +28,7 @@ class MockSocket : public Arcus::Socket void reset() override; // Catch these functions so that we can see whether they are called. - void sendMessage(Arcus::MessagePtr message) override; + bool sendMessage(Arcus::MessagePtr message) override; Arcus::MessagePtr takeNextMessage() override; // Helpers to store send and received messages. @@ -39,4 +40,4 @@ class MockSocket : public Arcus::Socket // NOLINTEND(misc-non-private-member-variables-in-classes) } // namespace cura -#endif // MOCKSOCKET_H \ No newline at end of file +#endif // MOCKSOCKET_H From 5024ded188cf2a10e476ae111e41d8459fae97da Mon Sep 17 00:00:00 2001 From: Remco Burema <41987080+rburema@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:54:38 +0200 Subject: [PATCH 040/139] Review: Better whitespace in comment. Co-authored-by: Jaime van Kessel --- include/LayerPlan.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index a2ab689f3b..697a20dfc1 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -902,8 +902,7 @@ class LayerPlan : public NoCopy * \param accelerate_length The pre-calculated length of the acceleration phase * \param end_speed_ratio The ratio of the top speed to be applied when finishing a segment * \param decelerate_length The pre-calculated length of the deceleration phase - * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or - * the normal first pass of the wall + * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or the normal first pass of the wall */ void addSplitWall( const ExtrusionLine& wall, From 23c45a061fc10c68345a5283e59bf4ace3aeeeea Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 1 Oct 2024 13:01:12 +0200 Subject: [PATCH 041/139] Make comment more clear for the new add-split-wall function. done as part of CURA-12077 --- include/LayerPlan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 697a20dfc1..0b5f632489 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -874,7 +874,7 @@ class LayerPlan : public NoCopy bool update_extrusion_offset = false); /*! - * \brief Add a wall to the gcode with optimized order + * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. * \param wall The full wall to be added * \param wall_length The pre-calculated full wall length * \param start_idx The index of the point where to start printing the wall From 54609c88b2ff06061fe2c07d7e6ab1e95288926d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 3 Oct 2024 15:45:22 +0200 Subject: [PATCH 042/139] Use specific grpc definitions --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index 909c921748..62a79abdae 100644 --- a/conandata.yml +++ b/conandata.yml @@ -4,6 +4,6 @@ requirements: requirements_arcus: - "arcus/5.4.1" requirements_plugins: - - "curaengine_grpc_definitions/0.2.1" + - "curaengine_grpc_definitions/0.2.1+910f59@ultimaker/cura_12081" requirements_cura_resources: - "cura_resources/(latest)@ultimaker/testing" From 5cfb461e201c0862268dcc91fba76fb48c15b500 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 4 Oct 2024 12:25:05 +0200 Subject: [PATCH 043/139] Revert "Use specific grpc definitions" This reverts commit 54609c88b2ff06061fe2c07d7e6ab1e95288926d. --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index 62a79abdae..909c921748 100644 --- a/conandata.yml +++ b/conandata.yml @@ -4,6 +4,6 @@ requirements: requirements_arcus: - "arcus/5.4.1" requirements_plugins: - - "curaengine_grpc_definitions/0.2.1+910f59@ultimaker/cura_12081" + - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - "cura_resources/(latest)@ultimaker/testing" From be7344e1959c381b63a6b0e8cac9ff6651c73068 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 7 Oct 2024 14:14:54 +0200 Subject: [PATCH 044/139] Change segment overhanging calculation formula CURA-12078 Instead of naively checking whether the start and end point of the segment are in the overhang area, calculate the intersection of the segment with the overhang area, and see if the length of the intersection is a significant enough part of the origin segment. This filters out some segments that partially overhang and are not really expected to be treated as overhanging. --- include/LayerPlan.h | 7 +++++++ src/LayerPlan.cpp | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 50195d7b36..48624a5afc 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -820,6 +820,13 @@ class LayerPlan : public NoCopy const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed); + + /*! + * \brief Calculates whether the given segment is to be treated as overhanging + * \param p0 The start point of the segment + * \param p1 The end point of the segment + */ + bool segmentIsOnOverhang(const Point2LL& p0, const Point2LL& p1) const; }; } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 271d29ff9a..e5b74a01b2 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -737,7 +737,7 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor); } distance_to_bridge_start -= len; @@ -752,7 +752,7 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor); } non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * width_factor * speed_factor * default_config.getSpeed(); cur_point = segment_end; @@ -839,14 +839,7 @@ void LayerPlan::addWallLine( else if (bridge_wall_mask_.empty()) { // no bridges required - addExtrusionMove( - p1, - default_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); + addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : 1.0_r); } else { @@ -1497,6 +1490,13 @@ void LayerPlan::addLinesInGivenOrder( } } +bool LayerPlan::segmentIsOnOverhang(const Point2LL& p0, const Point2LL& p1) const +{ + const OpenPolyline segment{ p0, p1 }; + const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); + return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; +} + void LayerPlan::addLinesMonotonic( const Shape& area, const OpenLinesSet& lines, From 04fc407e6ea3960d47bd7e1bf7b15d02c607902e Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Mon, 7 Oct 2024 12:43:43 +0000 Subject: [PATCH 045/139] Applied clang-format. --- include/LayerPlan.h | 2 +- src/LayerPlan.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index b67d2257bb..75eac440ea 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -948,7 +948,7 @@ class LayerPlan : public NoCopy * \param p0 The start point of the segment * \param p1 The end point of the segment */ - bool segmentIsOnOverhang(const Point3LL &p0, const Point3LL &p1) const; + bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const; }; } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1969524ee4..a821d01f4a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2387,7 +2387,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 5b260e1e86f4b0c3ca7237a6c1922d2066e552fa Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Tue, 8 Oct 2024 08:07:15 +0000 Subject: [PATCH 046/139] Set conan package version 5.9.0-alpha.1 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index 909c921748..6d61c67563 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-alpha.0" +version: "5.9.0-alpha.1" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: @@ -6,4 +6,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/(latest)@ultimaker/testing" + - "cura_resources/5.9.0-alpha.1" From 1cba80221353e3b0ccbb9ee531f5d5ceb92863a0 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Tue, 8 Oct 2024 08:10:56 +0000 Subject: [PATCH 047/139] Set conan package version 5.9.0-beta.1 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index 6d61c67563..98b837f655 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-alpha.1" +version: "5.9.0-beta.1" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: @@ -6,4 +6,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/5.9.0-alpha.1" + - "cura_resources/5.9.0-beta.1" From 43deebfdad7dbf72489102baf2f12d512a7bc787 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 9 Oct 2024 13:39:26 +0200 Subject: [PATCH 048/139] Start switch cooling before switching extruders CURA-12200 --- src/LayerPlan.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 8f6c12317c..c49b0c5317 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2294,6 +2294,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_nr = extruder_plan.extruder_nr_; gcode.ResetLastEValueAfterWipe(prev_extruder); + gcode.writePrepareFansForNozzleSwitch(); const RetractionAndWipeConfig& prev_retraction_config = storage_.retraction_wipe_config_per_extruder[prev_extruder]; if (prev_retraction_config.retraction_hop_after_extruder_switch) @@ -2306,8 +2307,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.switchExtruder(extruder_nr, prev_retraction_config.extruder_switch_retraction_config); } - gcode.writePrepareFansForNozzleSwitch(); - { // require printing temperature to be met constexpr bool wait = true; gcode.writeTemperatureCommand(extruder_nr, extruder_plan.required_start_temperature_, wait); @@ -2382,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From a1f65ab8a2a1053b07304004cd0dee9bf26a628f Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 9 Oct 2024 11:40:18 +0000 Subject: [PATCH 049/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c49b0c5317..705c557c2b 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 1752db52531887c67b7bbc4218cf85e1619537f8 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 9 Oct 2024 18:16:18 +0200 Subject: [PATCH 050/139] Fix 'encompassing hole' issue (tree support). When a hole is large enough to wholly contain (sometimes many) other brances, those also get cut. This prevents that. The solution isn't ideal, as there is an offset happening somewhere, but it's better than just missing the top of trees. CURA-12153 --- src/TreeSupport.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..783807e002 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2093,6 +2093,17 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) if (! found) { next_removed_holes_by_idx.emplace(idx); + + // Individual pieces of the hole could still be valid (if the 'hole' is made by branches surrounding others' for instance). + for (const auto& poly : hole) + { + if (poly.area() < 0) + { + auto poly_copy = poly; + poly_copy.reverse(); + valid_holes[layer_idx].push_back(poly_copy); + } + } } else { From 619088b58ff5efa84aea0f377842c03252dfebb2 Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 11:04:49 +0200 Subject: [PATCH 051/139] Add engine info handler for getting engine version and hash Implemented a handler for the `--engine_info_cb` flag to handle engine information retrieval. Added functionality to create and send engine info messages, which include the Cura engine version and hash, to Emscripten. Updated CMake and conan files to pass Cura engine hash. NP-349 --- CMakeLists.txt | 1 + conanfile.py | 1 + .../communication/EmscriptenCommunication.h | 4 ++- src/communication/CommandLine.cpp | 2 +- src/communication/EmscriptenCommunication.cpp | 30 +++++++++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d99b58a178..daf6022fbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,7 @@ target_compile_definitions(_CuraEngine $<$,$>:ENABLE_REMOTE_PLUGINS> $<$:OLDER_APPLE_CLANG> CURA_ENGINE_VERSION=\"${CURA_ENGINE_VERSION}\" + CURA_ENGINE_HASH=\"${CURA_ENGINE_HASH}\" $<$:BUILD_TESTS> PRIVATE $<$:NOMINMAX> diff --git a/conanfile.py b/conanfile.py index d40e9c70c4..9f2b96c9c7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -140,6 +140,7 @@ def generate(self): tc = CMakeToolchain(self) tc.variables["CURA_ENGINE_VERSION"] = self.version + tc.variables["CURA_ENGINE_HASH"] = self.conan_data["commit"] tc.variables["ENABLE_ARCUS"] = self.options.enable_arcus tc.variables["ENABLE_TESTING"] = not self.conf.get("tools.build:skip_test", False, check_type=bool) tc.variables["ENABLE_BENCHMARKS"] = self.options.enable_benchmarks diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 5144b96dc6..30b09dd622 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -23,12 +23,14 @@ class EmscriptenCommunication : public CommandLine std::string progress_handler_; ///< Handler for progress messages. std::string gcode_header_handler_; ///< Handler for getting the GCode handler. std::string slice_info_handler_; ///< Handler for slice information messages. - + std::string engine_info_handler_; ///< Handler for curaengine info : version and hash. /** * \brief Creates a message containing slice information. * \return A string containing the slice information message. */ [[nodiscard]] static std::string createSliceInfoMessage(); + [[nodiscard]] static std::string createEngineInfoMessage(); + public: /** diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index e5f8b4340a..ae30bfae88 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -186,7 +186,7 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } - else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb")) + else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") || argument.starts_with("engine_info_cb")) { // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 8155290ee9..820b95623e 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -38,6 +38,10 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& { gcode_header_handler_ = *ranges::next(gcode_header_flag); } + if (auto engine_info_flag = ranges::find(arguments_, "--engine_info_cb"); engine_info_flag != arguments_.end()) + { + engine_info_handler_ = *ranges::next(engine_info_flag); + } } void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const @@ -109,9 +113,35 @@ std::string EmscriptenCommunication::createSliceInfoMessage() return buffer.GetString(); } +std::string EmscriptenCommunication::createEngineInfoMessage() +{ + // Construct a string with rapidjson containing the engine information + rapidjson::Document doc; + auto& allocator = doc.GetAllocator(); + doc.SetObject(); + + // Set the slicer version + rapidjson::Value version("version", allocator); + rapidjson::Value version_value(CURA_ENGINE_VERSION, allocator); + doc.AddMember(version, version_value, allocator); + + // Set the hash + rapidjson::Value hash("hash", allocator); + rapidjson::Value hash_value(CURA_ENGINE_HASH, allocator); + doc.AddMember(hash, hash_value, allocator); + + // Serialize the JSON document to a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + void EmscriptenCommunication::sliceNext() { CommandLine::sliceNext(); + auto engine_info = createEngineInfoMessage(); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", engine_info_handler_, engine_info).c_str()); auto slice_info = createSliceInfoMessage(); emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", slice_info_handler_, slice_info).c_str()); }; From 4ae78aa48763cf1ed7235e7a7f84eff4c092c55a Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 09:05:29 +0000 Subject: [PATCH 052/139] Applied clang-format. --- src/communication/CommandLine.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index ae30bfae88..def4b7864f 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -186,7 +186,9 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } - else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") || argument.starts_with("engine_info_cb")) + else if ( + argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") + || argument.starts_with("engine_info_cb")) { // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; From f9ebce702a1efd261150df3f9f95f9265472075f Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 11:53:48 +0200 Subject: [PATCH 053/139] Add beginGCode function to EmscriptenCommunication Introduced a new function `beginGCode` to indicate the start of g-code sending with curaengine info. NP-349 --- include/communication/EmscriptenCommunication.h | 5 +++++ src/communication/EmscriptenCommunication.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 30b09dd622..91901d8955 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -50,6 +50,11 @@ class EmscriptenCommunication : public CommandLine */ void sendGCodePrefix(const std::string& prefix) const override; + /** + * \brief Indicate that we're beginning to send g-code. + */ + void beginGCode() override; + /** * \brief Initiates the slicing of the next item. */ diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 820b95623e..6b84fae2af 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -137,11 +137,14 @@ std::string EmscriptenCommunication::createEngineInfoMessage() return buffer.GetString(); } -void EmscriptenCommunication::sliceNext() +void EmscriptionCommunication::beginGCode() { - CommandLine::sliceNext(); auto engine_info = createEngineInfoMessage(); emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", engine_info_handler_, engine_info).c_str()); +} +void EmscriptenCommunication::sliceNext() +{ + CommandLine::sliceNext(); auto slice_info = createSliceInfoMessage(); emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", slice_info_handler_, slice_info).c_str()); }; From dc900d426353b01bdd86b05f666996a4d24651ad Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 09:54:27 +0000 Subject: [PATCH 054/139] Applied clang-format. --- include/communication/EmscriptenCommunication.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 91901d8955..b1c7e7c903 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -50,7 +50,7 @@ class EmscriptenCommunication : public CommandLine */ void sendGCodePrefix(const std::string& prefix) const override; - /** + /** * \brief Indicate that we're beginning to send g-code. */ void beginGCode() override; From 45c585e3b3669f90b15e1f0084e184a0efd1ed23 Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 12:05:52 +0200 Subject: [PATCH 055/139] Add commit hash to conandata.yml Include a 'commit' field in conandata.yml to store the commit hash. NP-349 --- conandata.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/conandata.yml b/conandata.yml index 909c921748..9d41341189 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,5 @@ version: "5.9.0-alpha.0" +commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: From 916a92aabe7fada3fbfa2f1507969eb8edbaa2af Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 12:48:04 +0200 Subject: [PATCH 056/139] fix typo NP-349 --- src/communication/EmscriptenCommunication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 6b84fae2af..e62e68cf32 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -137,7 +137,7 @@ std::string EmscriptenCommunication::createEngineInfoMessage() return buffer.GetString(); } -void EmscriptionCommunication::beginGCode() +void EmscriptenCommunication::beginGCode() { auto engine_info = createEngineInfoMessage(); emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", engine_info_handler_, engine_info).c_str()); From 6c4fad3399212ac85815623e2a560b02c5151def Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 10 Oct 2024 13:03:21 +0200 Subject: [PATCH 057/139] fix argument NP-349 --- src/communication/CommandLine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index def4b7864f..da4bef97e5 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -188,7 +188,7 @@ void CommandLine::sliceNext() } else if ( argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") - || argument.starts_with("engine_info_cb")) + || argument.starts_with("--engine_info_cb")) { // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; From e3ac68ce68fe2e9c586c669004de368d2782e270 Mon Sep 17 00:00:00 2001 From: nallath Date: Thu, 10 Oct 2024 12:09:58 +0000 Subject: [PATCH 058/139] Applied clang-format. --- src/gcodeExport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index d42c15d670..a6b224c312 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -698,7 +698,7 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons processInitialLayerTemperature(storage, start_extruder_nr); } - if (!mesh_group_settings.get("machine_start_gcode_first")) + if (! mesh_group_settings.get("machine_start_gcode_first")) { writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); } From 239f2ad6fa15e6d1b8e634e12761cb668c1ada64 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 15 Oct 2024 08:16:32 +0200 Subject: [PATCH 059/139] Set different seam strategy for inner walls after outer walls CURA-12164 --- include/PathOrderOptimizer.h | 59 +++++++++++++++++++++++++++--------- include/path_ordering.h | 10 ++++++ src/InsetOrderOptimizer.cpp | 6 ++-- src/LayerPlan.cpp | 2 +- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index be8e61873b..634e493480 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -113,7 +113,8 @@ class PathOrderOptimizer const bool reverse_direction = false, const std::unordered_multimap& order_requirements = no_order_requirements_, const bool group_outer_walls = false, - const Shape& disallowed_areas_for_seams = {}) + const Shape& disallowed_areas_for_seams = {}, + const bool use_shortest_for_inner_walls = false) : start_point_(start_point) , seam_config_(seam_config) , combing_boundary_((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) @@ -122,7 +123,7 @@ class PathOrderOptimizer , _group_outer_walls(group_outer_walls) , order_requirements_(&order_requirements) , disallowed_area_for_seams{ disallowed_areas_for_seams } - + , use_shortest_for_inner_walls_(use_shortest_for_inner_walls) { } @@ -130,11 +131,12 @@ class PathOrderOptimizer * Add a new polygon to be optimized. * \param polygon The polygon to optimize. */ - void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt) + void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt, const bool is_outer_wall = false) { constexpr bool is_closed = true; paths_.emplace_back(polygon, is_closed); paths_.back().force_start_index_ = force_start_index; + paths_.back().is_outer_wall = is_outer_wall; } /*! @@ -180,6 +182,23 @@ class PathOrderOptimizer } } + // Set actual used start point calculation strategy for each path + const OrderablePath* previous_path = nullptr; + for (auto& path : paths_) + { + if (use_shortest_for_inner_walls_ && previous_path && previous_path->is_outer_wall && ! path.is_outer_wall) + { + path.seam_config_ = ZSeamConfig(EZSeamType::SHORTEST); + path.force_start_index_ = std::nullopt; + } + else + { + path.seam_config_ = seam_config_; + } + + previous_path = &path; + } + // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); @@ -205,16 +224,19 @@ class PathOrderOptimizer // For some Z seam types the start position can be pre-computed. // This is faster since we don't need to re-compute the start position at each step then. - precompute_start &= seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (precompute_start) { for (auto& path : paths_) { - if (! path.is_closed_ || path.converted_->empty()) + if (path.seam_config_.type_ == EZSeamType::RANDOM || path.seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER) { - continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + if (! path.is_closed_ || path.converted_->empty()) + { + continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + } + path.start_vertex_ = findStartLocation(path, path.seam_config_.pos_); } - path.start_vertex_ = findStartLocation(path, seam_config_.pos_); } } @@ -298,6 +320,12 @@ class PathOrderOptimizer */ const std::unordered_multimap* order_requirements_; + /*! + * If true, we will compute the seam position of inner walls using a "shortest" seam configs, for inner walls that + * are directly following an outer wall. + */ + const bool use_shortest_for_inner_walls_; + std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { std::vector optimized_order; // To store our result in. @@ -583,8 +611,8 @@ class PathOrderOptimizer continue; } - const bool precompute_start - = seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; + const bool precompute_start = path->seam_config_.type_ == EZSeamType::RANDOM || path->seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path->seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (! path->is_closed_ || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex_ = findStartLocation(*path, start_position); @@ -690,7 +718,7 @@ class PathOrderOptimizer // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. - if (seam_config_.type_ == EZSeamType::RANDOM) + if (path.seam_config_.type_ == EZSeamType::RANDOM) { size_t vert = getRandomPointInPolygon(*path.converted_); return vert; @@ -720,9 +748,10 @@ class PathOrderOptimizer { // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. // For SHARPEST_CORNER, use a fixed starting score of 0. - const double score_distance = (seam_config_.type_ == EZSeamType::SHARPEST_CORNER && seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - ? MM2INT(10) - : vSize2(here - target_pos); + const double score_distance + = (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + ? MM2INT(10) + : vSize2(here - target_pos); double corner_angle = cornerAngle(path, i, segments_sizes, total_length); // angles < 0 are concave (left turning) @@ -730,7 +759,7 @@ class PathOrderOptimizer double corner_shift; - if (seam_config_.type_ == EZSeamType::SHORTEST) + if (path.seam_config_.type_ == EZSeamType::SHORTEST) { // the more a corner satisfies our criteria, the closer it appears to be // shift 10mm for a very acute corner @@ -746,7 +775,7 @@ class PathOrderOptimizer } double score = score_distance; - switch (seam_config_.corner_pref_) + switch (path.seam_config_.corner_pref_) { default: case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: diff --git a/include/path_ordering.h b/include/path_ordering.h index 0a7fb1af13..9366ba44c8 100644 --- a/include/path_ordering.h +++ b/include/path_ordering.h @@ -82,6 +82,16 @@ struct PathOrdering */ std::optional force_start_index_; + /*! + * The start point calculation strategy to be used for this path + */ + ZSeamConfig seam_config_; + + /*! + * Indicates whether this path is an outer (or inner) wall + */ + bool is_outer_wall{ false }; + /*! * Get vertex data from the custom path type. * diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 56961e2bde..1edf0b1a87 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -93,6 +93,7 @@ bool InsetOrderOptimizer::addToLayer() const bool current_extruder_is_wall_x = wall_x_extruder_nr_ == extruder_nr_; const bool reverse = shouldReversePath(use_one_extruder, current_extruder_is_wall_x, outer_to_inner); + const bool use_shortest_for_inner_walls = ! pack_by_inset && outer_to_inner; auto walls_to_be_added = getWallsToBeAdded(reverse, use_one_extruder); const auto order = pack_by_inset ? getInsetOrder(walls_to_be_added, outer_to_inner) : getRegionOrder(walls_to_be_added, outer_to_inner); @@ -114,7 +115,8 @@ bool InsetOrderOptimizer::addToLayer() reverse, order, group_outer_walls, - disallowed_areas_for_seams_); + disallowed_areas_for_seams_, + use_shortest_for_inner_walls); for (auto& line : walls_to_be_added) { @@ -126,7 +128,7 @@ bool InsetOrderOptimizer::addToLayer() // If the user indicated that we may deviate from the vertices for the seam, we can insert a seam point, if needed. force_start = insertSeamPoint(line); } - order_optimizer.addPolygon(&line, force_start); + order_optimizer.addPolygon(&line, force_start, line.is_outer_wall()); } else { diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index a821d01f4a..1969524ee4 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2387,7 +2387,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From e3c506824759f89f86cba14590586ebc2a3a6e87 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 16 Oct 2024 14:33:52 +0200 Subject: [PATCH 060/139] Uniformize score calculation for seam vertex selection CURA-12164 We now use a unified score for all the criteria when selecting the best candidate vertex for the seam. This way we can more easily add new critera. At the same time, this scoring is now used to exclude the vertices that are on overhang areas. We need to do this at the same time as the regular seam calculation so that it can properly mix with other criteria. --- include/InsetOrderOptimizer.h | 4 +- include/LayerPlan.h | 49 +--------- include/PathOrderOptimizer.h | 166 +++++++++++++++++++-------------- include/utils/AABB.h | 7 +- include/utils/CriterionScore.h | 21 +++++ include/utils/Score.h | 54 +++++++++++ include/utils/math.h | 13 ++- src/FffGcodeWriter.cpp | 3 +- src/InsetOrderOptimizer.cpp | 7 +- src/LayerPlan.cpp | 10 +- src/utils/AABB.cpp | 6 +- 11 files changed, 210 insertions(+), 130 deletions(-) create mode 100644 include/utils/CriterionScore.h create mode 100644 include/utils/Score.h diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index afba5dc452..b3f8573c60 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -59,7 +59,8 @@ class InsetOrderOptimizer const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams = {}, const bool scarf_seam = false, - const bool smooth_speed = false); + const bool smooth_speed = false, + const Shape& overhang_areas = Shape()); /*! * Adds the insets to the given layer plan. @@ -114,6 +115,7 @@ class InsetOrderOptimizer Shape disallowed_areas_for_seams_; const bool scarf_seam_; const bool smooth_speed_; + Shape overhang_areas_; std::vector> inset_polys_; // vector of vectors holding the inset polygons Shape retraction_region_; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 75eac440ea..c167eb8d3e 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -298,6 +298,11 @@ class LayerPlan : public NoCopy */ void setSeamOverhangMask(const Shape& polys); + /*! + * Get the areas that are considered having air below, which is a unite between bridge and overhang masks + */ + Shape getAirBelowMask() const; + /*! * Set roofing_mask. * @@ -677,50 +682,6 @@ class LayerPlan : public NoCopy const bool is_top_layer, const bool is_bottom_layer); - - /*! - * Given a wall polygon and a start vertex index, return the index of the first vertex that is supported (is not above air) - * - * Uses bridge_wall_mask and overhang_mask to determine where there is air below - * - * \param wall The wall polygon - * \param start_idx The index of the starting vertex of \p wall - * \return The index of the first supported vertex - if no vertices are supported, start_idx is returned - */ - template - size_t locateFirstSupportedVertex(const T& wall, const size_t start_idx) const - { - if (bridge_wall_mask_.empty() && seam_overhang_mask_.empty()) - { - return start_idx; - } - - const auto air_below = bridge_wall_mask_.unionPolygons(seam_overhang_mask_); - - size_t curr_idx = start_idx; - - while (true) - { - const Point2LL& vertex = cura::make_point(wall[curr_idx]); - if (! air_below.inside(vertex, true)) - { - // vertex isn't above air so it's OK to use - return curr_idx; - } - - if (++curr_idx >= wall.size()) - { - curr_idx = 0; - } - - if (curr_idx == start_idx) - { - // no vertices are supported so just return the original index - return start_idx; - } - } - } - /*! * Write the planned paths to gcode * diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 634e493480..c90a0be009 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -21,7 +21,9 @@ #include "path_ordering.h" #include "settings/EnumSettings.h" //To get the seam settings. #include "settings/ZSeamConfig.h" //To read the seam configuration. +#include "utils/Score.h" #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. +#include "utils/math.h" #include "utils/polygonUtils.h" #include "utils/views/dfs.h" @@ -114,7 +116,8 @@ class PathOrderOptimizer const std::unordered_multimap& order_requirements = no_order_requirements_, const bool group_outer_walls = false, const Shape& disallowed_areas_for_seams = {}, - const bool use_shortest_for_inner_walls = false) + const bool use_shortest_for_inner_walls = false, + const Shape& overhang_areas = Shape()) : start_point_(start_point) , seam_config_(seam_config) , combing_boundary_((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) @@ -124,6 +127,7 @@ class PathOrderOptimizer , order_requirements_(&order_requirements) , disallowed_area_for_seams{ disallowed_areas_for_seams } , use_shortest_for_inner_walls_(use_shortest_for_inner_walls) + , overhang_areas_(overhang_areas) { } @@ -183,10 +187,9 @@ class PathOrderOptimizer } // Set actual used start point calculation strategy for each path - const OrderablePath* previous_path = nullptr; for (auto& path : paths_) { - if (use_shortest_for_inner_walls_ && previous_path && previous_path->is_outer_wall && ! path.is_outer_wall) + if (use_shortest_for_inner_walls_ && ! path.is_outer_wall) { path.seam_config_ = ZSeamConfig(EZSeamType::SHORTEST); path.force_start_index_ = std::nullopt; @@ -195,8 +198,6 @@ class PathOrderOptimizer { path.seam_config_ = seam_config_; } - - previous_path = &path; } // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. @@ -326,6 +327,11 @@ class PathOrderOptimizer */ const bool use_shortest_for_inner_walls_; + /*! + * Contains the overhang areas, where we would prefer not to place the start locations of walls + */ + const Shape overhang_areas_; + std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { std::vector optimized_order; // To store our result in. @@ -742,94 +748,114 @@ class PathOrderOptimizer total_length += segment_size; } - size_t best_i; - double best_score = std::numeric_limits::infinity(); + // If seam is not "shortest", we still compute the shortest distance score but with a very low weight + const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? 1.0 : 0.02; + + // Corner strategy has a standard weight + constexpr double weight_corner = 1.0; + + // Avoiding overhangs is more important than the rest + constexpr double weight_exclude_overhang = 2.0; + + // In order to avoid jumping seams, we give a small score to the vertex X and Y position, so that if we have + // e.g. multiple corners with the same angle, we will always choose the ones at the top-right + constexpr double weight_consistency_x = 0.05; + constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects + + // Fixed divider for shortests distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0 + constexpr double distance_divider = 20.0; + + const AABB path_bounding_box(*path.converted_); + + std::optional best_i; + Score best_score; for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - // For SHARPEST_CORNER, use a fixed starting score of 0. - const double score_distance - = (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - ? MM2INT(10) - : vSize2(here - target_pos); + Score vertex_score; - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + // For SHARPEST_CORNER, use a fixed score of 0. + if (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + { + CriterionScore score_distance{ .weight = weight_distance }; - double corner_shift; + // Use actual (non-squared) distance to ensure a proper scoring distribution + const double distance = vSizeMM(here - target_pos); + // Use reciprocal function to normalize distance score decreasingly + score_distance.score = 1.0 / (1.0 + (distance / distance_divider)); - if (path.seam_config_.type_ == EZSeamType::SHORTEST) - { - // the more a corner satisfies our criteria, the closer it appears to be - // shift 10mm for a very acute corner - corner_shift = MM2INT(10) * MM2INT(10); + vertex_score += score_distance; } - else + + if (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN) { - // the larger the distance from prev_point to p1, the more a corner will "attract" the seam - // so the user has some control over where the seam will lie. + double corner_angle = cornerAngle(path, i, segments_sizes, total_length); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) - // the divisor here may need adjusting to obtain the best results (TBD) - corner_shift = score_distance / 50; - } + CriterionScore score_corner{ .weight = weight_corner }; - double score = score_distance; - switch (path.seam_config_.corner_pref_) - { - default: - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score += corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score -= corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - score -= std::abs(corner_angle) * corner_shift; // Still give sharper corners more advantage. - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: // Give sharper corners some advantage, but sharper concave corners even more. - { - double score_corner = std::abs(corner_angle) * corner_shift; - if (corner_angle < 0) // Concave corner. + switch (path.seam_config_.corner_pref_) { - score_corner *= 2; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score_corner.score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score_corner.score = -corner_angle; + } + else + { + score_corner.score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; } - score -= score_corner; - break; - } + + vertex_score += score_corner; } - constexpr double EPSILON = 5.0; - if (std::abs(best_score - score) <= EPSILON) + CriterionScore score_consistency_x{ .weight = weight_consistency_x }; + score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); + vertex_score += score_consistency_x; + + CriterionScore score_consistency_y{ .weight = weight_consistency_y }; + score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); + vertex_score += score_consistency_y; + + if (! overhang_areas_.empty()) { - // 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 - const Point2LL& best_point = path.converted_->at(best_i); - if (std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) - { - best_score = std::min(best_score, score); - best_i = i; - } + CriterionScore score_exclude_overhang{ .weight = weight_exclude_overhang }; + score_exclude_overhang.score = overhang_areas_.inside(here, true) ? 0.0 : 1.0; + vertex_score += score_exclude_overhang; } - else if (score < best_score) + + if (! best_i.has_value() || vertex_score > best_score) { best_i = i; - best_score = score; + best_score = vertex_score; } } if (! disallowed_area_for_seams.empty()) { - best_i = pathIfZseamIsInDisallowedArea(best_i, path, 0); + best_i = pathIfZseamIsInDisallowedArea(best_i.value_or(0), path, 0); } - return best_i; + return best_i.value_or(0); } /*! diff --git a/include/utils/AABB.h b/include/utils/AABB.h index e3a93c678b..6be6ba9b2c 100644 --- a/include/utils/AABB.h +++ b/include/utils/AABB.h @@ -9,6 +9,7 @@ namespace cura { +class PointsSet; class Polygon; class Shape; @@ -21,10 +22,10 @@ class AABB AABB(); //!< initializes with invalid min and max AABB(const Point2LL& min, const Point2LL& max); //!< initializes with given min and max AABB(const Shape& shape); //!< Computes the boundary box for the given shape - AABB(const Polygon& poly); //!< Computes the boundary box for the given polygons + AABB(const PointsSet& poly); //!< Computes the boundary box for the given polygons void calculate(const Shape& shape); //!< Calculates the aabb for the given shape (throws away old min and max data of this aabb) - void calculate(const Polygon& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) + void calculate(const PointsSet& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) /*! * Whether the bounding box contains the specified point. @@ -80,7 +81,7 @@ class AABB */ void include(const Point2LL& point); - void include(const Polygon& polygon); + void include(const PointsSet& polygon); /*! * \brief Includes the specified bounding box in the bounding box. diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h new file mode 100644 index 0000000000..8d700802b6 --- /dev/null +++ b/include/utils/CriterionScore.h @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_CRITERION_SCORE_H +#define UTILS_CRITERION_SCORE_H + +#include + +#include "SparsePointGrid.h" + +namespace cura +{ + +struct CriterionScore +{ + double score{ 0.0 }; + double weight{ 0.0 }; +}; + +} // namespace cura +#endif // UTILS_CRITERION_SCORE_H diff --git a/include/utils/Score.h b/include/utils/Score.h new file mode 100644 index 0000000000..0710323968 --- /dev/null +++ b/include/utils/Score.h @@ -0,0 +1,54 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORE_H +#define UTILS_SCORE_H + +#include + +#include "CriterionScore.h" + +namespace cura +{ + +class Score +{ +private: + double value_{ 0.0 }; + +public: + double getValue() const + { + return value_; + } + + void operator+=(const CriterionScore& criterion_score) + { + value_ += criterion_score.score * criterion_score.weight; + } + + auto operator<=>(const Score&) const = default; + + double operator-(const Score& other) const + { + return value_ - other.value_; + } +}; + +} // namespace cura + +namespace fmt +{ + +template<> +struct formatter : formatter +{ + auto format(const cura::Score& score, format_context& ctx) + { + return fmt::format_to(ctx.out(), "Score{{{}}}", score.getValue()); + } +}; + +} // namespace fmt + +#endif // UTILS_SCORE_H diff --git a/include/utils/math.h b/include/utils/math.h index f1ad772c72..d044915889 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -25,7 +25,6 @@ template { return a * a; } - /** * @brief Returns the quotient of the division of two signed integers, rounded to the nearest integer. * @@ -108,5 +107,17 @@ template return (dividend + divisor - 1) / divisor; } +[[nodiscard]] inline double inverse_lerp(double a, double b, double v) +{ + if (a == b) + { + return 0.0; + } + else + { + return (v - a) / (b - a); + } +} + } // namespace cura #endif // UTILS_MATH_H diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 9a7f26a410..1eca2f45ee 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3148,7 +3148,8 @@ bool FffGcodeWriter::processInsets( mesh.bounding_box.flatten().getMiddle(), disallowed_areas_for_seams, scarf_seam, - smooth_speed); + smooth_speed, + gcode_layer.getAirBelowMask()); added_something |= wall_orderer.addToLayer(); } return added_something; diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 1edf0b1a87..fed5ff2201 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -54,7 +54,8 @@ InsetOrderOptimizer::InsetOrderOptimizer( const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams, const bool scarf_seam, - const bool smooth_speed) + const bool smooth_speed, + const Shape& overhang_areas) : gcode_writer_(gcode_writer) , storage_(storage) , gcode_layer_(gcode_layer) @@ -78,6 +79,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( , disallowed_areas_for_seams_{ disallowed_areas_for_seams } , scarf_seam_(scarf_seam) , smooth_speed_(smooth_speed) + , overhang_areas_(overhang_areas) { } @@ -116,7 +118,8 @@ bool InsetOrderOptimizer::addToLayer() order, group_outer_walls, disallowed_areas_for_seams_, - use_shortest_for_inner_walls); + use_shortest_for_inner_walls, + overhang_areas_); for (auto& line : walls_to_be_added) { diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1969524ee4..d3c28a7c45 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1328,11 +1328,6 @@ void LayerPlan::addWall( { return; } - if (is_closed) - { - // make sure wall start point is not above air! - start_idx = locateFirstSupportedVertex(wall, start_idx); - } const bool actual_scarf_seam = scarf_seam && is_closed; double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output @@ -3050,6 +3045,11 @@ void LayerPlan::setSeamOverhangMask(const Shape& polys) seam_overhang_mask_ = polys; } +Shape LayerPlan::getAirBelowMask() const +{ + return bridge_wall_mask_.unionPolygons(seam_overhang_mask_); +} + void LayerPlan::setRoofingMask(const Shape& polys) { roofing_mask_ = polys; diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 163c6a5f26..5487673fd0 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -33,7 +33,7 @@ AABB::AABB(const Shape& shape) calculate(shape); } -AABB::AABB(const Polygon& poly) +AABB::AABB(const PointsSet &poly) : min_(POINT_MAX, POINT_MAX) , max_(POINT_MIN, POINT_MIN) { @@ -83,7 +83,7 @@ void AABB::calculate(const Shape& shape) } } -void AABB::calculate(const Polygon& poly) +void AABB::calculate(const PointsSet &poly) { min_ = Point2LL(POINT_MAX, POINT_MAX); max_ = Point2LL(POINT_MIN, POINT_MIN); @@ -141,7 +141,7 @@ void AABB::include(const Point2LL& point) max_.Y = std::max(max_.Y, point.Y); } -void AABB::include(const Polygon& polygon) +void AABB::include(const PointsSet &polygon) { for (const Point2LL& point : polygon) { From de47e0c5d5299def5730e79b70d1fcfcb55b1626 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 16 Oct 2024 15:30:11 +0200 Subject: [PATCH 061/139] Fix overhang seam interaction with user-defined position CURA-12164 --- include/InsetOrderOptimizer.h | 2 +- include/PathOrderOptimizer.h | 123 ++++++++++++++++++---------------- src/InsetOrderOptimizer.cpp | 19 +++--- 3 files changed, 77 insertions(+), 67 deletions(-) diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index b3f8573c60..80c0856f3d 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -130,7 +130,7 @@ class InsetOrderOptimizer * * \param closed_line The polygon to insert the seam point in. (It's assumed to be closed at least.) * - * \return The index of the inserted seam point, or std::nullopt if no seam point was inserted. + * \return The index of the inserted seam point, or the index of the closest point if an existing one can be used. */ std::optional insertSeamPoint(ExtrusionLine& closed_line); diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index c90a0be009..92d1e28c47 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -730,12 +730,6 @@ class PathOrderOptimizer return vert; } - if (path.force_start_index_.has_value()) - { - // Start index already known, since we forced it, return. - return path.force_start_index_.value(); - } - // Precompute segments lengths because we are going to need them multiple times std::vector segments_sizes(path.converted_->size()); coord_t total_length = 0; @@ -754,6 +748,9 @@ class PathOrderOptimizer // Corner strategy has a standard weight constexpr double weight_corner = 1.0; + // User-set position has a standard weight + constexpr double weight_user_position = 1.0; + // Avoiding overhangs is more important than the rest constexpr double weight_exclude_overhang = 2.0; @@ -767,6 +764,15 @@ class PathOrderOptimizer constexpr double distance_divider = 20.0; const AABB path_bounding_box(*path.converted_); + const Point2LL forced_start_pos = path.force_start_index_.has_value() ? path.converted_->at(path.force_start_index_.value()) : Point2LL(); + + auto scoreFromDistance = [&distance_divider](const Point2LL& here, const Point2LL& remote_pos) -> double + { + // Use actual (non-squared) distance to ensure a proper scoring distribution + const double distance = vSizeMM(here - remote_pos); + // Use reciprocal function to normalize distance score decreasingly + return 1.0 / (1.0 + (distance / distance_divider)); + }; std::optional best_i; Score best_score; @@ -774,68 +780,69 @@ class PathOrderOptimizer { Score vertex_score; - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - // For SHARPEST_CORNER, use a fixed score of 0. - if (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + if (path.force_start_index_.has_value()) { - CriterionScore score_distance{ .weight = weight_distance }; - - // Use actual (non-squared) distance to ensure a proper scoring distribution - const double distance = vSizeMM(here - target_pos); - // Use reciprocal function to normalize distance score decreasingly - score_distance.score = 1.0 / (1.0 + (distance / distance_divider)); - - vertex_score += score_distance; + vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_user_position }; } - - if (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN) + else { - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) - - CriterionScore score_corner{ .weight = weight_corner }; + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + // For SHARPEST_CORNER, use a fixed score of 0. + if (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + { + vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; + } - switch (path.seam_config_.corner_pref_) + if (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)) { - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - // Still give sharper corners more advantage. - score_corner.score = std::abs(corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: - // Give sharper corners some advantage, but sharper concave corners even more. - if (corner_angle < 0) - { - score_corner.score = -corner_angle; - } - else + double corner_angle = cornerAngle(path, i, segments_sizes, total_length); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) + + CriterionScore score_corner{ .weight = weight_corner }; + + switch (path.seam_config_.corner_pref_) { - score_corner.score = corner_angle / 2.0; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score_corner.score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score_corner.score = -corner_angle; + } + else + { + score_corner.score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; } - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - case EZSeamCornerPrefType::PLUGIN: - break; - } - vertex_score += score_corner; - } + vertex_score += score_corner; + } - CriterionScore score_consistency_x{ .weight = weight_consistency_x }; - score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); - vertex_score += score_consistency_x; + CriterionScore score_consistency_x{ .weight = weight_consistency_x }; + score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); + vertex_score += score_consistency_x; - CriterionScore score_consistency_y{ .weight = weight_consistency_y }; - score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); - vertex_score += score_consistency_y; + CriterionScore score_consistency_y{ .weight = weight_consistency_y }; + score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); + vertex_score += score_consistency_y; + } if (! overhang_areas_.empty()) { diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index fed5ff2201..116e200187 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -207,7 +207,7 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed Point2LL closest_point; size_t closest_junction_idx = 0; coord_t closest_distance_sqd = std::numeric_limits::max(); - bool should_reclaculate_closest = false; + bool should_recalculate_closest = false; if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED) { // For user-defined seams you usually don't _actually_ want the _closest_ point, per-se, @@ -253,24 +253,27 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed closest_junction_idx = i; } } - should_reclaculate_closest = true; + should_recalculate_closest = true; } const auto& start_pt = closed_line.junctions_[closest_junction_idx]; const auto& end_pt = closed_line.junctions_[(closest_junction_idx + 1) % closed_line.junctions_.size()]; - if (should_reclaculate_closest) + if (should_recalculate_closest) { // In the second case (see above) the closest point hasn't actually been calculated yet, // since in that case we'de need the start and end points. So do that here. closest_point = LinearAlg2D::getClosestOnLineSegment(request_point, start_pt.p_, end_pt.p_); } constexpr coord_t smallest_dist_sqd = 25; - if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd || vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd) { - // Early out if the closest point is too close to the start or end point. - // NOTE: Maybe return the index here anyway, since this is the point the current caller would want to force the seam to. - // However, then the index returned would have a caveat that it _can_ point to an already exisiting point then. - return std::nullopt; + // If the closest point is very close to the start point, just use it instead. + return closest_junction_idx; + } + if (vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + { + // If the closest point is very close to the end point, just use it instead. + return (closest_junction_idx + 1) % closed_line.junctions_.size(); } // NOTE: This could also be done on a single axis (skipping the implied sqrt), but figuring out which one and then using the right values became a bit messy/verbose. From 4f99edc675e0799c7920c62d8df7bfe0071ce4c9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 16 Oct 2024 16:55:02 +0200 Subject: [PATCH 062/139] Add documentation CURA-12164 --- include/PathOrderOptimizer.h | 5 ++--- include/utils/CriterionScore.h | 14 ++++++++++++++ include/utils/Score.h | 17 ++++++++++++----- include/utils/math.h | 19 +++++++++++++------ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 92d1e28c47..e39aab2a37 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -759,8 +759,8 @@ class PathOrderOptimizer constexpr double weight_consistency_x = 0.05; constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects - // Fixed divider for shortests distances computation. The divider should be set so that the minimum encountered - // distance gives a score very close to 1.0 + // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 constexpr double distance_divider = 20.0; const AABB path_bounding_box(*path.converted_); @@ -787,7 +787,6 @@ class PathOrderOptimizer else { // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - // For SHARPEST_CORNER, use a fixed score of 0. if (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) { vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h index 8d700802b6..708db3f05a 100644 --- a/include/utils/CriterionScore.h +++ b/include/utils/CriterionScore.h @@ -11,9 +11,23 @@ namespace cura { +/*! + * This structure represents a score given by a single crtierion when calculating a global score to select a best + * candidate among a list with multiple criteria. + */ struct CriterionScore { + /*! + * The score given by the criterion. To ensure a proper selection, this value must be contained in [0.0, 1.0] and + * the different given scores must be evenly distributed in this range. + */ double score{ 0.0 }; + + /*! + * The weight to be given when taking this score into the global score. A score that contributes "normally" to the + * global score should have a weight of 1.0, and others should be adjusted around this value, to give them more or + * less influence. + */ double weight{ 0.0 }; }; diff --git a/include/utils/Score.h b/include/utils/Score.h index 0710323968..f8108498ee 100644 --- a/include/utils/Score.h +++ b/include/utils/Score.h @@ -11,28 +11,35 @@ namespace cura { +/*! + * This class represents a score to be calculated over different criteria, to select the best candidate among a list. + */ class Score { private: double value_{ 0.0 }; public: + /*! + * Get the actual score value, should be used for debug purposes only + */ double getValue() const { return value_; } + /*! + * Add the calculated score of an inidividual criterion to the global score, taking care of its weight + */ void operator+=(const CriterionScore& criterion_score) { value_ += criterion_score.score * criterion_score.weight; } + /*! + * Comparison operators to allow selecting the best global score + */ auto operator<=>(const Score&) const = default; - - double operator-(const Score& other) const - { - return value_ - other.value_; - } }; } // namespace cura diff --git a/include/utils/math.h b/include/utils/math.h index d044915889..efe5de76f4 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -107,16 +107,23 @@ template return (dividend + divisor - 1) / divisor; } -[[nodiscard]] inline double inverse_lerp(double a, double b, double v) +/*! + * \brief Calculates the "inverse linear interpolation" of a value over a range, i.e. given a range [min, max] the + * value "min" would give a result of 0.0 and the value "max" would give a result of 1.0, values in between will + * be interpolated linearly. + * \note The returned value may be out of the [0.0, 1.0] range if the given value is outside the [min, max] range, it is + * up to the caller to clamp the result if required + * \note The range_min value may be greater than the range_max, inverting the interpolation logic + */ +template +[[nodiscard]] inline double inverse_lerp(T range_min, T range_max, T value) { - if (a == b) + if (range_min == range_max) { return 0.0; } - else - { - return (v - a) / (b - a); - } + + return static_cast(value - range_min) / (range_max - range_min); } } // namespace cura From 11811e194918d935c767b72e1384f5fe0cd2dbe4 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 16 Oct 2024 14:55:38 +0000 Subject: [PATCH 063/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- src/utils/AABB.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index d3c28a7c45..0949ca21df 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2382,7 +2382,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 5487673fd0..36cfa32e98 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -33,7 +33,7 @@ AABB::AABB(const Shape& shape) calculate(shape); } -AABB::AABB(const PointsSet &poly) +AABB::AABB(const PointsSet& poly) : min_(POINT_MAX, POINT_MAX) , max_(POINT_MIN, POINT_MIN) { @@ -83,7 +83,7 @@ void AABB::calculate(const Shape& shape) } } -void AABB::calculate(const PointsSet &poly) +void AABB::calculate(const PointsSet& poly) { min_ = Point2LL(POINT_MAX, POINT_MAX); max_ = Point2LL(POINT_MIN, POINT_MIN); @@ -141,7 +141,7 @@ void AABB::include(const Point2LL& point) max_.Y = std::max(max_.Y, point.Y); } -void AABB::include(const PointsSet &polygon) +void AABB::include(const PointsSet& polygon) { for (const Point2LL& point : polygon) { From ae1f6d2e6f5df9c6bddb3cd2b4f022f10948f4dd Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 16 Oct 2024 17:07:25 +0200 Subject: [PATCH 064/139] Clean code CURA-12164 --- include/utils/CriterionScore.h | 4 ---- include/utils/math.h | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h index 708db3f05a..e4313d988e 100644 --- a/include/utils/CriterionScore.h +++ b/include/utils/CriterionScore.h @@ -4,10 +4,6 @@ #ifndef UTILS_CRITERION_SCORE_H #define UTILS_CRITERION_SCORE_H -#include - -#include "SparsePointGrid.h" - namespace cura { diff --git a/include/utils/math.h b/include/utils/math.h index efe5de76f4..50b02669b9 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -25,6 +25,7 @@ template { return a * a; } + /** * @brief Returns the quotient of the division of two signed integers, rounded to the nearest integer. * From edeaf0a3a4e67ef6728da6713c809bb19d9b43ed Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 16 Oct 2024 18:35:21 +0200 Subject: [PATCH 065/139] Fix polygonorderoptimizer unit-test. done as part of CURA-12164 --- tests/PathOrderOptimizerTest.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/PathOrderOptimizerTest.cpp b/tests/PathOrderOptimizerTest.cpp index c8621ba3d8..97cc1617c5 100644 --- a/tests/PathOrderOptimizerTest.cpp +++ b/tests/PathOrderOptimizerTest.cpp @@ -12,25 +12,17 @@ namespace cura class PathOrderOptimizerTest : public testing::Test { public: - /*! - * A blank optimizer with no polygons added yet. Fresh and virgin. - */ - PathOrderOptimizer optimizer; - /*! * A simple isosceles triangle. Base length and height 50. */ Polygon triangle; PathOrderOptimizerTest() - : optimizer(Point2LL(0, 0)) { } void SetUp() override { - optimizer = PathOrderOptimizer(Point2LL(0, 0)); - triangle.clear(); triangle.push_back(Point2LL(0, 0)); triangle.push_back(Point2LL(50, 0)); @@ -44,6 +36,7 @@ class PathOrderOptimizerTest : public testing::Test */ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); optimizer.optimize(); // Don't crash. EXPECT_EQ(optimizer.paths_.size(), 0) << "Still empty!"; } @@ -54,6 +47,8 @@ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) */ TEST_F(PathOrderOptimizerTest, ThreeTrianglesShortestOrder) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); + Polygon near = triangle; // Copy, then translate. near.translate(Point2LL(100, 100)); Polygon middle = triangle; From a9124567017c18198d4efa495c09355b24d2323f Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 17 Oct 2024 08:35:59 +0200 Subject: [PATCH 066/139] Fix comment typo CURA-12164 --- include/LayerPlan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index c167eb8d3e..7cf67e2c42 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -299,7 +299,7 @@ class LayerPlan : public NoCopy void setSeamOverhangMask(const Shape& polys); /*! - * Get the areas that are considered having air below, which is a unite between bridge and overhang masks + * Get the areas that are considered having air below, which is a union between bridge and overhang masks */ Shape getAirBelowMask() const; From 5cedf8616213c0f49dc13059eb9fe7d1af2f2f65 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 17 Oct 2024 16:51:24 +0200 Subject: [PATCH 067/139] Prevent implicit unsigned to signed conversions Co-authored-by: @ToyboxZach --- src/FffGcodeWriter.cpp | 4 ++-- src/PrimeTower.cpp | 2 +- src/PrimeTower/PrimeTower.cpp | 2 +- src/raft.cpp | 13 +++++-------- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 9a7f26a410..c7a67dd923 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -173,7 +173,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep { if (extruder_is_used_in_filler_layers) { - process_layer_starting_layer_nr = -Raft::getFillerLayerCount(); + process_layer_starting_layer_nr = -static_cast(Raft::getFillerLayerCount()); break; } } @@ -2860,7 +2860,7 @@ size_t FffGcodeWriter::findUsedExtruderIndex(const SliceDataStorage& storage, co { return last ? extruder_use.back().extruder_nr : extruder_use.front().extruder_nr; } - else if (layer_nr <= -Raft::getTotalExtraLayers()) + else if (layer_nr <= -LayerIndex(Raft::getTotalExtraLayers())) { // Asking for extruder use below first layer, give first extruder return getStartExtruder(storage); diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index bc4e2a07a7..3b7933235f 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -604,7 +604,7 @@ const Shape& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const const Shape& PrimeTower::getGroundPoly() const { - return getOuterPoly(-Raft::getTotalExtraLayers()); + return getOuterPoly(-LayerIndex(Raft::getTotalExtraLayers())); } void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const diff --git a/src/PrimeTower/PrimeTower.cpp b/src/PrimeTower/PrimeTower.cpp index 074d1f501a..30b8ab7eb7 100644 --- a/src/PrimeTower/PrimeTower.cpp +++ b/src/PrimeTower/PrimeTower.cpp @@ -351,7 +351,7 @@ bool PrimeTower::extruderRequiresPrime(const std::vector& extruder_is_used void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const size_t extruder_nr) const { LayerIndex layer_nr = gcode_layer.getLayerNr(); - if (layer_nr != -Raft::getTotalExtraLayers()) + if (layer_nr != -LayerIndex(Raft::getTotalExtraLayers())) { coord_t wipe_radius; auto iterator = base_occupied_outline_.iterator_at(gcode_layer.getLayerNr()); diff --git a/src/raft.cpp b/src/raft.cpp index edaff83509..f4d40eb8f0 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -199,26 +199,23 @@ Raft::LayerType Raft::getLayerType(LayerIndex layer_index) const auto interface_layers = Raft::getInterfaceLayers(); const auto surface_layers = Raft::getSurfaceLayers(); - if (layer_index < -airgap - surface_layers - interface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers + interface_layers)) { return LayerType::RaftBase; } - else if (layer_index < -airgap - surface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers)) { return LayerType::RaftInterface; } - else if (layer_index < -airgap) + if (layer_index < -LayerIndex(airgap)) { return LayerType::RaftSurface; } - else if (layer_index < 0) + if (layer_index < LayerIndex(0)) { return LayerType::Airgap; } - else - { - return LayerType::Model; - } + return LayerType::Model; } size_t Raft::getLayersAmount(const std::string& extruder_nr_setting_name, const std::string& target_raft_section) From ca5adba38cea5ce574abce4f03fb89edfa9e94d7 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 07:47:03 +0200 Subject: [PATCH 068/139] Add method to write a generic polyline, and better handle flushing CURA-12207 --- include/utils/SVG.h | 9 ++++--- src/utils/SVG.cpp | 60 ++++++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/include/utils/SVG.h b/include/utils/SVG.h index 96bcfa0a45..7105468f10 100644 --- a/include/utils/SVG.h +++ b/include/utils/SVG.h @@ -62,6 +62,7 @@ class SVG : NoCopy private: std::string toString(const Color color) const; std::string toString(const ColorObject& color) const; + void handleFlush(const bool flush) const; FILE* out_; // the output file const AABB aabb_; // the boundary box to display @@ -122,7 +123,7 @@ class SVG : NoCopy */ void writeLines(const std::vector& polyline, const ColorObject color = Color::BLACK) const; - void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const double head_size = 5.0) const; @@ -148,10 +149,12 @@ class SVG : NoCopy void writePolygon(Polygon poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; - void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writePolyline(const Polygon& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolyline(const Polyline& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; + /*! * Draw variable-width paths into the image. * @@ -183,7 +186,7 @@ class SVG : NoCopy * \param color The color to draw the line with. * \param width_factor A multiplicative factor on the line width. */ - void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0) const; + void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0, const bool flush = true) const; /*! * Draws a grid across the image and writes down coordinates. diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp index 69a115d9d4..5e281c05cc 100644 --- a/src/utils/SVG.cpp +++ b/src/utils/SVG.cpp @@ -59,16 +59,23 @@ std::string SVG::toString(const ColorObject& color) const } } +void SVG::handleFlush(const bool flush) const +{ + if (flush) + { + fflush(out_); + } +} + SVG::SVG(std::string filename, AABB aabb, Point2LL canvas_size, ColorObject background) - : SVG( - filename, - aabb, - std::min( - static_cast(canvas_size.X - canvas_size.X / 5 * 2) / static_cast(aabb.max_.X - aabb.min_.X), - static_cast(canvas_size.Y - canvas_size.Y / 5) / static_cast(aabb.max_.Y - aabb.min_.Y)), - canvas_size, - background) + : SVG(filename, + aabb, + std::min( + static_cast(canvas_size.X - canvas_size.X / 5 * 2) / static_cast(aabb.max_.X - aabb.min_.X), + static_cast(canvas_size.Y - canvas_size.Y / 5) / static_cast(aabb.max_.Y - aabb.min_.Y)), + canvas_size, + background) { } @@ -249,7 +256,7 @@ void SVG::writeLines(const std::vector& polyline, const ColorObject co fprintf(out_, "\" />\n"); // Write the end of the tag. } -void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width) const +void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const bool flush) const { Point3D fa = transformF(a); Point3D fb = transformF(b); @@ -262,6 +269,8 @@ void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject colo static_cast(fb.y_), toString(color).c_str(), static_cast(stroke_width)); + + handleFlush(flush); } void SVG::writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const double head_size) const @@ -342,10 +351,7 @@ void SVG::writePolygons(const Shape& polys, const ColorObject color, const doubl writePolygon(poly, color, stroke_width, false); } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } void SVG::writePolygon(const Polygon poly, const ColorObject color, const double stroke_width, const bool flush) const @@ -381,19 +387,18 @@ void SVG::writePolygon(const Polygon poly, const ColorObject color, const double i++; } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } -void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width) const +void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width, const bool flush) const { for (const Polygon& poly : polys) { - writePolyline(poly, color, stroke_width); + writePolyline(poly, color, stroke_width, false); } + + handleFlush(flush); } void SVG::writePolyline(const Polygon& poly, const ColorObject color, const double stroke_width) const @@ -427,6 +432,16 @@ void SVG::writePolyline(const Polygon& poly, const ColorObject color, const doub } } +void SVG::writePolyline(const Polyline& poly, const ColorObject color, const double stroke_width, const bool flush) const +{ + for (auto iterator = poly.beginSegments(); iterator != poly.endSegments(); ++iterator) + { + writeLine((*iterator).start, (*iterator).end, color, stroke_width, false); + } + + handleFlush(flush); +} + void SVG::writePaths(const std::vector& paths, const ColorObject color, const double width_factor) const { for (const VariableWidthLines& lines : paths) @@ -443,7 +458,7 @@ void SVG::writeLines(const VariableWidthLines& lines, const ColorObject color, c } } -void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor) const +void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor, const bool flush) const { constexpr double minimum_line_width = 10; // Always have some width, otherwise some lines become completely invisible. if (line.junctions_.empty()) // Only draw lines that have at least 2 junctions, otherwise they are degenerate. @@ -481,6 +496,11 @@ void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const do start_vertex = end_vertex; // For the next line segment. } + + if (flush) + { + fflush(out_); + } } void SVG::writeCoordinateGrid(const coord_t grid_size, const Color color, const double stroke_width, const double font_size) const From 0e6e6a3ec9ee5bb2a0e3378adeb2b8ee3f44010d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 07:52:02 +0200 Subject: [PATCH 069/139] Change extra infill lines splitting strategy CURA-12207 The previous strategy was to split the two crossed segments and join them betweem each other, printing the joined section twice. Although this is very efficient in terms of path, having a segment printed twice is sometimes an issue when there is actually some material under the segment. The new strategy is to create multiple separate paths, which should create more travel nove but no more segment duplication. --- src/FffGcodeWriter.cpp | 83 ++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 9a7f26a410..5877d6732c 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2064,14 +2064,10 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& // We do this because supporting lines are hanging over air, // and therefore print best as part of a continuous print move, // rather than having a travel move before and after them. -// -// We also double-insert most lines, since that allows the -// elimination of all travel moves, and overlap isn't an issue -// because the lines are hanging. void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& line_to_add) { // Returns the line index and the index of the point within an infill_line, null for no match found. - const auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](Point2LL p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { @@ -2101,59 +2097,76 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin { /* both ends intersect with the same line. * If the inserted line has ends x, y - * and the original line was A--(x)--B---C--(y)--D - * Then the new line will be A--x--y--C---B--x--y--D - * Note that the middle part of the line is reversed. + * and the original line was ...--A--(x)--B--...--C--(y)--D--... + * Then the new lines will be ...--A--x--y--C--...--B--x + * And line y--D--... + * Note that some parts of the line are reversed, + * and the last one is completly split apart. */ OpenPolyline& old_line = infill_lines[front_line_index]; - OpenPolyline new_line; + OpenPolyline new_line_start; + OpenPolyline new_line_end; Point2LL x, y; - size_t x_index, y_index; + std::ptrdiff_t x_index, y_index; if (front_point_index < back_point_index) { x = line_to_add.front(); y = line_to_add.back(); - x_index = front_point_index; - y_index = back_point_index; + x_index = static_cast(front_point_index); + y_index = static_cast(back_point_index); } else { y = line_to_add.front(); x = line_to_add.back(); - y_index = front_point_index; - x_index = back_point_index; + y_index = static_cast(front_point_index); + x_index = static_cast(back_point_index); } - new_line.insert(new_line.end(), old_line.begin(), old_line.begin() + x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.rend() - y_index, old_line.rend() - x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.begin() + y_index, old_line.end()); - old_line.setPoints(std::move(new_line.getPoints())); + + new_line_start.insert(new_line_start.end(), old_line.begin(), old_line.begin() + x_index); + new_line_start.push_back(x); + new_line_start.push_back(y); + new_line_start.insert(new_line_start.end(), old_line.rend() - y_index, old_line.rend() - x_index); + new_line_start.push_back(x); + + new_line_end.push_back(y); + new_line_end.insert(new_line_end.end(), old_line.begin() + y_index, old_line.end()); + + old_line.setPoints(std::move(new_line_start.getPoints())); + infill_lines.push_back(new_line_end); } else { /* Different lines * If the line_to_add has ends [front, back] - * Existing line (intersects front): A B front C D E - * Other existing line (intersects back): M N back O P Q - * Result is Line: A B front back O P Q - * And line: M N back front C D E + * Existing line (intersects front): ...--A--(x)--B--... + * Other existing line (intersects back): ...--C--(y)--D--... + * Result is Line: ...--A--x--y--D--... + * And line: x--B--... + * And line: ...--C--y */ OpenPolyline& old_front = infill_lines[front_line_index]; OpenPolyline& old_back = infill_lines[back_line_index]; - OpenPolyline new_front, new_back; - new_front.insert(new_front.end(), old_front.begin(), old_front.begin() + front_point_index); - new_front.push_back(line_to_add.front()); - new_front.push_back(line_to_add.back()); - new_front.insert(new_front.end(), old_back.begin() + back_point_index, old_back.end()); - new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + back_point_index); - new_back.push_back(line_to_add.back()); - new_back.push_back(line_to_add.front()); - new_back.insert(new_back.end(), old_front.begin() + front_point_index, old_front.end()); + OpenPolyline full_line, new_front, new_back; + const Point2LL x = line_to_add.front(); + const Point2LL y = line_to_add.back(); + const auto x_index = static_cast(front_point_index); + const auto y_index = static_cast(back_point_index); + + new_front.push_back(x); + new_front.insert(new_front.end(), old_front.begin() + x_index, old_front.end()); + + new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + y_index); + new_back.push_back(y); + + full_line.insert(full_line.end(), old_front.begin(), old_front.begin() + x_index); + full_line.push_back(x); + full_line.push_back(y); + full_line.insert(full_line.end(), old_back.begin() + y_index, old_back.end()); + old_front.setPoints(std::move(new_front.getPoints())); old_back.setPoints(std::move(new_back.getPoints())); + infill_lines.push_back(full_line); } } else From 0f896bd254f7e8e8f0bf2003f062dd49a7988033 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 18 Oct 2024 05:52:31 +0000 Subject: [PATCH 070/139] Applied clang-format. --- src/utils/SVG.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp index 5e281c05cc..83f223e9b1 100644 --- a/src/utils/SVG.cpp +++ b/src/utils/SVG.cpp @@ -69,13 +69,14 @@ void SVG::handleFlush(const bool flush) const SVG::SVG(std::string filename, AABB aabb, Point2LL canvas_size, ColorObject background) - : SVG(filename, - aabb, - std::min( - static_cast(canvas_size.X - canvas_size.X / 5 * 2) / static_cast(aabb.max_.X - aabb.min_.X), - static_cast(canvas_size.Y - canvas_size.Y / 5) / static_cast(aabb.max_.Y - aabb.min_.Y)), - canvas_size, - background) + : SVG( + filename, + aabb, + std::min( + static_cast(canvas_size.X - canvas_size.X / 5 * 2) / static_cast(aabb.max_.X - aabb.min_.X), + static_cast(canvas_size.Y - canvas_size.Y / 5) / static_cast(aabb.max_.Y - aabb.min_.Y)), + canvas_size, + background) { } From a474a2b94e8f4e5a74bf6fed59e8a8f3308aa961 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 09:01:52 +0200 Subject: [PATCH 071/139] Change enabling shortest for inner walls strategy CURA-12164 With or without "Optimize Wall Printing Order", it remains valid to have the "shortest" seam strategy for inner walls. It may not work in some situations, but will do its best and should actually work in all common cases. --- src/InsetOrderOptimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 116e200187..7c8723bb95 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -95,7 +95,7 @@ bool InsetOrderOptimizer::addToLayer() const bool current_extruder_is_wall_x = wall_x_extruder_nr_ == extruder_nr_; const bool reverse = shouldReversePath(use_one_extruder, current_extruder_is_wall_x, outer_to_inner); - const bool use_shortest_for_inner_walls = ! pack_by_inset && outer_to_inner; + const bool use_shortest_for_inner_walls = outer_to_inner; auto walls_to_be_added = getWallsToBeAdded(reverse, use_one_extruder); const auto order = pack_by_inset ? getInsetOrder(walls_to_be_added, outer_to_inner) : getRegionOrder(walls_to_be_added, outer_to_inner); From 10faafa8b9dc4d7fc4fbb690f9960cec2a46a3a8 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 10:24:58 +0200 Subject: [PATCH 072/139] Introduce random scoring in seam candidate calculation CURA-12164 This way the "random" strategy is also confonted to overhang checking, so that when having the choice between multiple random points if will select those not overhanging when possible. Also partially rewrote the score calculation code to make it very clear what scores are calculated dependending on the settings. This also allows doing long pre-calculations only when really required. --- include/PathOrderOptimizer.h | 182 +++++++++++++++++++---------------- include/utils/math.h | 9 ++ 2 files changed, 107 insertions(+), 84 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index e39aab2a37..6afaa5dae4 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -724,32 +724,13 @@ class PathOrderOptimizer // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. - if (path.seam_config_.type_ == EZSeamType::RANDOM) - { - size_t vert = getRandomPointInPolygon(*path.converted_); - return vert; - } - - // Precompute segments lengths because we are going to need them multiple times - std::vector segments_sizes(path.converted_->size()); - coord_t total_length = 0; - for (size_t i = 0; i < path.converted_->size(); ++i) - { - const Point2LL& here = path.converted_->at(i); - const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); - const coord_t segment_size = vSize(next - here); - segments_sizes[i] = segment_size; - total_length += segment_size; - } + // ########## Step 1: define the weighs of each criterion + // Standard weight for the "main" selection criterion, depending on the selected strategy. There should be + // exactly one calculation using this criterion. + constexpr double weight_main_criterion = 1.0; // If seam is not "shortest", we still compute the shortest distance score but with a very low weight - const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? 1.0 : 0.02; - - // Corner strategy has a standard weight - constexpr double weight_corner = 1.0; - - // User-set position has a standard weight - constexpr double weight_user_position = 1.0; + const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? weight_main_criterion : 0.02; // Avoiding overhangs is more important than the rest constexpr double weight_exclude_overhang = 2.0; @@ -759,81 +740,124 @@ class PathOrderOptimizer constexpr double weight_consistency_x = 0.05; constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects - // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered - // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 - constexpr double distance_divider = 20.0; + // ########## Step 2: define which criteria should be taken into account in the total score + const bool calculate_forced_pos_score = path.force_start_index_.has_value(); + + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + const bool calculate_distance_score + = ! calculate_forced_pos_score + && (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE); + + const bool calculate_corner_score + = ! calculate_forced_pos_score + && (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)); + + const bool calculate_random_score = ! calculate_forced_pos_score && (path.seam_config_.type_ == EZSeamType::RANDOM); + + const bool calculate_consistency_score = calculate_distance_score || calculate_corner_score; + + // Whatever the strategy, always avoid overhang + const bool calculate_overhang_score = ! overhang_areas_.empty(); + + // ########## Step 3: calculate some values that we are going to need multiple times, depending on which scoring is active + const AABB path_bounding_box = calculate_consistency_score ? AABB(*path.converted_) : AABB(); - const AABB path_bounding_box(*path.converted_); const Point2LL forced_start_pos = path.force_start_index_.has_value() ? path.converted_->at(path.force_start_index_.value()) : Point2LL(); - auto scoreFromDistance = [&distance_divider](const Point2LL& here, const Point2LL& remote_pos) -> double + std::vector segments_sizes; + coord_t total_length = 0; + if (calculate_corner_score) + { + segments_sizes.resize(path.converted_->size()); + for (size_t i = 0; i < path.converted_->size(); ++i) + { + const Point2LL& here = path.converted_->at(i); + const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); + const coord_t segment_size = vSize(next - here); + segments_sizes[i] = segment_size; + total_length += segment_size; + } + } + + auto scoreFromDistance = [](const Point2LL& here, const Point2LL& remote_pos) -> double { + // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + constexpr double distance_divider = 20.0; + // Use actual (non-squared) distance to ensure a proper scoring distribution const double distance = vSizeMM(here - remote_pos); + // Use reciprocal function to normalize distance score decreasingly return 1.0 / (1.0 + (distance / distance_divider)); }; + // ########## Step 4: now calculate the total score of each vertex and select the best one std::optional best_i; Score best_score; for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) { Score vertex_score; - if (path.force_start_index_.has_value()) + if (calculate_forced_pos_score) { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_user_position }; + vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_main_criterion }; } - else + + if (calculate_distance_score) { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - if (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; - } + vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; + } - if (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER - && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)) - { - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) + if (calculate_corner_score) + { + double corner_angle = cornerAngle(path, i, segments_sizes, total_length); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) - CriterionScore score_corner{ .weight = weight_corner }; + CriterionScore score_corner{ .weight = weight_main_criterion }; - switch (path.seam_config_.corner_pref_) + switch (path.seam_config_.corner_pref_) + { + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score_corner.score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) { - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - // Still give sharper corners more advantage. - score_corner.score = std::abs(corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: - // Give sharper corners some advantage, but sharper concave corners even more. - if (corner_angle < 0) - { - score_corner.score = -corner_angle; - } - else - { - score_corner.score = corner_angle / 2.0; - } - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - case EZSeamCornerPrefType::PLUGIN: - break; + score_corner.score = -corner_angle; } - - vertex_score += score_corner; + else + { + score_corner.score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; } + vertex_score += score_corner; + } + + if (calculate_random_score) + { + vertex_score += CriterionScore{ .score = cura::randf(), .weight = weight_main_criterion }; + } + + if (calculate_consistency_score) + { CriterionScore score_consistency_x{ .weight = weight_consistency_x }; score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); vertex_score += score_consistency_x; @@ -843,7 +867,7 @@ class PathOrderOptimizer vertex_score += score_consistency_y; } - if (! overhang_areas_.empty()) + if (calculate_overhang_score) { CriterionScore score_exclude_overhang{ .weight = weight_exclude_overhang }; score_exclude_overhang.score = overhang_areas_.inside(here, true) ? 0.0 : 1.0; @@ -1000,16 +1024,6 @@ class PathOrderOptimizer return sum * sum; // Squared distance, for fair comparison with direct distance. } - /*! - * Get a random vertex of a polygon. - * \param polygon A polygon to get a random vertex of. - * \return A random index in that polygon. - */ - size_t getRandomPointInPolygon(const PointsSet& polygon) const - { - return rand() % polygon.size(); - } - bool isLoopingPolyline(const OrderablePath& path) { if (path.converted_->empty()) diff --git a/include/utils/math.h b/include/utils/math.h index 50b02669b9..95306cf9b9 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -127,5 +127,14 @@ template return static_cast(value - range_min) / (range_max - range_min); } +/*! + * \brief Get a random floating point number in the range [0.0, 1.0] + */ +template +[[nodiscard]] inline T randf() +{ + return static_cast(std::rand()) / static_cast(RAND_MAX); +} + } // namespace cura #endif // UTILS_MATH_H From 03de538b6e8c8440aa10b025f5b7f903bde94445 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 12:42:11 +0200 Subject: [PATCH 073/139] Apply simplification suggested by @casperlamboo CURA-12164 --- include/PathOrderOptimizer.h | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 6afaa5dae4..d7ad78eea8 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -743,17 +743,20 @@ class PathOrderOptimizer // ########## Step 2: define which criteria should be taken into account in the total score const bool calculate_forced_pos_score = path.force_start_index_.has_value(); - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - const bool calculate_distance_score - = ! calculate_forced_pos_score - && (path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE); + bool calculate_distance_score = false; + bool calculate_corner_score = false; + bool calculate_random_score = false; + if (! calculate_forced_pos_score) + { + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + calculate_distance_score = path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; - const bool calculate_corner_score - = ! calculate_forced_pos_score - && (path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER - && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)); + calculate_corner_score + = path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN); - const bool calculate_random_score = ! calculate_forced_pos_score && (path.seam_config_.type_ == EZSeamType::RANDOM); + calculate_random_score = path.seam_config_.type_ == EZSeamType::RANDOM; + } const bool calculate_consistency_score = calculate_distance_score || calculate_corner_score; From b0cf410da985e750e8bc506d657454886c1b1caa Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 12:58:15 +0200 Subject: [PATCH 074/139] Remove interaction between briding and seam overhang CURA-12164 --- include/LayerPlan.h | 4 ++-- src/FffGcodeWriter.cpp | 2 +- src/LayerPlan.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 7cf67e2c42..0be204faf2 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -299,9 +299,9 @@ class LayerPlan : public NoCopy void setSeamOverhangMask(const Shape& polys); /*! - * Get the areas that are considered having air below, which is a union between bridge and overhang masks + * Get the seam overhang mask, which contains the areas where we don't want to place the seam because they are overhanding */ - Shape getAirBelowMask() const; + const Shape& getSeamOverhangMask() const; /*! * Set roofing_mask. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 1eca2f45ee..847352aaa4 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3149,7 +3149,7 @@ bool FffGcodeWriter::processInsets( disallowed_areas_for_seams, scarf_seam, smooth_speed, - gcode_layer.getAirBelowMask()); + gcode_layer.getSeamOverhangMask()); added_something |= wall_orderer.addToLayer(); } return added_something; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 45a3c52ac7..e78896ee8b 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -3044,9 +3044,9 @@ void LayerPlan::setSeamOverhangMask(const Shape& polys) seam_overhang_mask_ = polys; } -Shape LayerPlan::getAirBelowMask() const +const Shape& LayerPlan::getSeamOverhangMask() const { - return bridge_wall_mask_.unionPolygons(seam_overhang_mask_); + return seam_overhang_mask_; } void LayerPlan::setRoofingMask(const Shape& polys) From 68b7f52ead677225a8a14d8f4dc1648bc262f03f Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 18 Oct 2024 10:58:46 +0000 Subject: [PATCH 075/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index e78896ee8b..2b17229c36 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 1a0f74193e0953fd57049c18b0d93161d6775d5a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 18 Oct 2024 14:23:56 +0200 Subject: [PATCH 076/139] Fix build with old compilers CURA-12164 --- include/utils/math.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/utils/math.h b/include/utils/math.h index 95306cf9b9..a0650f0e8f 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -130,7 +130,7 @@ template /*! * \brief Get a random floating point number in the range [0.0, 1.0] */ -template +template [[nodiscard]] inline T randf() { return static_cast(std::rand()) / static_cast(RAND_MAX); From 762f8483015b5058820f58b7b34d660fdaed694e Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:37:17 +0200 Subject: [PATCH 077/139] Revert "Fix 'encompassing hole' issue (tree support)." This reverts commit 1752db52531887c67b7bbc4218cf85e1619537f8. --- src/TreeSupport.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 783807e002..67a68e1cf6 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2093,17 +2093,6 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) if (! found) { next_removed_holes_by_idx.emplace(idx); - - // Individual pieces of the hole could still be valid (if the 'hole' is made by branches surrounding others' for instance). - for (const auto& poly : hole) - { - if (poly.area() < 0) - { - auto poly_copy = poly; - poly_copy.reverse(); - valid_holes[layer_idx].push_back(poly_copy); - } - } } else { From bd1fc3e8e7e9b557956abdb880427740391683cc Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:29:02 +0200 Subject: [PATCH 078/139] Revert "Tree-support: Create more dissalowed area; prevents model-intersection." This reverts commit 885352e9 --- src/TreeSupport.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..21a34796de 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,8 +2023,10 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } - const Shape& relevant_forbidden = volumes_.getCollision(0, layer_idx, true); - Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); + Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) + .createTubeShape( + closing_dist, + 0); //.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); Shape holes_below; @@ -2041,11 +2043,6 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { holes_resting_outside[layer_idx].emplace(idx); } - else if (hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).area() > hole.length() * EPSILON) - { - holes_resting_outside[layer_idx].emplace( - idx); // technically not resting outside, also not valid, but the alternative is potentially having lines go though the model - } else { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) From 3600ea9514696d1da0e548a34c6b15b57fd7cc0f Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:29:02 +0200 Subject: [PATCH 079/139] Fix issue causing support lines to overlap with the model --- src/TreeSupport.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 21a34796de..7d5380478a 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,6 +2023,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } + Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) .createTubeShape( closing_dist, @@ -2043,6 +2044,11 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { holes_resting_outside[layer_idx].emplace(idx); } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).offset(-config.xy_min_distance / 2).empty()) + { + // technically not resting outside, also not valid, but the alternative is potentially having lines go through the model + holes_resting_outside[layer_idx].emplace(idx); + } else { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) From 469be9c4ec047209e3a44abfa6f5ee657d0e5789 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:46:36 +0200 Subject: [PATCH 080/139] Fix issue with tree support hole removal if the hole compasses other branches. Prevents removal of holes with holes. Also added a performance improvement found while debugging. --- src/TreeSupport.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 7d5380478a..ead27ed820 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2024,10 +2024,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) } Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); - Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) - .createTubeShape( - closing_dist, - 0); //.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); Shape holes_below; @@ -2040,7 +2037,13 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); - if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) + if(hole.size() > 1) + { + // The hole contains other branches! It can not be fully removed. + // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring larger changes. + holes_resting_outside[layer_idx].emplace(idx); + } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) { holes_resting_outside[layer_idx].emplace(idx); } @@ -2053,8 +2056,9 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { + // TODO should technically be outline: Check if this is fine either way as it would save an offset if (hole_aabb.hit(AABB(hole2)) - && ! hole.intersection(hole2).empty()) // TODO should technically be outline: Check if this is fine either way as it would save an offset + && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) { hole_rest_map[layer_idx][idx].emplace_back(idx2); } From 8de3babdb0a6360b477956ba3b0cb4501e9ff877 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:11:01 +0200 Subject: [PATCH 081/139] Restore a small change mistakenly undone by a previous commit. --- src/TreeSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index ead27ed820..8268bc2e9c 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,7 +2023,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } - Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); + const Shape& relevant_forbidden = volumes_.getCollision(0, layer_idx, true); Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); Shape holes_below; From 4774868ae054929dcf212442b2db0929e779ad02 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 22 Oct 2024 11:49:12 +0200 Subject: [PATCH 082/139] Set dev version 5.10 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index a44c2c758b..65017ec7a9 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-beta.1" +version: "5.10.0-alpha.0" commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" @@ -7,4 +7,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/5.9.0-beta.1" + - "cura_resources/(latest)@ultimaker/testing" From dcdeff62f3e0548ac2d290bc855ad9a8deb89cf9 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 23 Oct 2024 09:02:10 +0200 Subject: [PATCH 083/139] Only perform 'tiny loops' fix if there's space to do so. Previously, we created this 'resolveIntersection' function to handle infill line intersections that where very close to the walls and would therefore create 'tiny loops', which created problems, especially when extruding, like for connect infill lines. However, the alternative maneuvre (or at least the calculation thereof) needs at least _some_ space to work in. If the crossing to be prevented is 'on' the line (within an epsilon), the calculation will not be possible in the current state -- and if things are that close, the rest of the algos will take care of it. CURA-12173 --- src/infill.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/infill.cpp b/src/infill.cpp index 67fdd613c9..469c4035b8 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -931,12 +931,16 @@ void Infill::connectLines(OpenLinesSet& result_lines) } else { + constexpr coord_t epsilon_sqd = 25; + // Resolve any intersections of the fill lines close to the boundary, by inserting extra points so the lines don't create a tiny 'loop'. Point2LL intersect; if (vSize2(previous_point - next_point) < half_line_distance_squared && LinearAlg2D::lineLineIntersection(previous_segment->start_, previous_segment->end_, crossing->start_, crossing->end_, intersect) && LinearAlg2D::pointIsProjectedBeyondLine(intersect, previous_segment->start_, previous_segment->end_) == 0 - && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0) + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 + && vSize2(previous_point - intersect) > epsilon_sqd + && vSize2(next_point - intersect) > epsilon_sqd) { resolveIntersection(infill_line_width_, intersect, previous_point, next_point, previous_segment, crossing); } From 6db4b4ff830da77500dbcdceab9ddf6ef8949c9b Mon Sep 17 00:00:00 2001 From: rburema Date: Wed, 23 Oct 2024 07:02:54 +0000 Subject: [PATCH 084/139] Applied clang-format. --- src/infill.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/infill.cpp b/src/infill.cpp index 469c4035b8..695e7a134c 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -938,8 +938,7 @@ void Infill::connectLines(OpenLinesSet& result_lines) if (vSize2(previous_point - next_point) < half_line_distance_squared && LinearAlg2D::lineLineIntersection(previous_segment->start_, previous_segment->end_, crossing->start_, crossing->end_, intersect) && LinearAlg2D::pointIsProjectedBeyondLine(intersect, previous_segment->start_, previous_segment->end_) == 0 - && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 - && vSize2(previous_point - intersect) > epsilon_sqd + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 && vSize2(previous_point - intersect) > epsilon_sqd && vSize2(next_point - intersect) > epsilon_sqd) { resolveIntersection(infill_line_width_, intersect, previous_point, next_point, previous_segment, crossing); From 0119f39db3028382f6ec6b9ab70f836c710a1416 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 23 Oct 2024 16:37:09 +0200 Subject: [PATCH 085/139] Fix scarf seam and coasting interaction CURA-12175 This is basically a rewrite of the coasting implementation. The coasting is now fully pre-calculated before processing the paths, because we need to do it partially anyway, then the second part just applies the result of the calculations. --- include/ExtruderPlan.h | 2 +- include/LayerPlan.h | 76 ++++++---- include/geometry/Point3LL.h | 5 + include/utils/Coord_t.h | 19 ++- src/LayerPlan.cpp | 278 +++++++++++++++++------------------- src/gcodeExport.cpp | 2 +- 6 files changed, 196 insertions(+), 186 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index b5c579496c..cfd2070975 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -191,7 +191,7 @@ class ExtruderPlan /*! * @return distance between p0 and p1 as well as the time spend on the segment */ - std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path); + std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const; /*! * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0be204faf2..d373fff7ae 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -57,20 +57,27 @@ class LayerPlan : public NoCopy #endif public: - // 'AdjustCoasting'; because split-up paths from the same extruder (with no travel moves between them) should count as the same path w.r.t. coasting. - enum class AdjustCoasting - { - AsNormal, - CoastEntirePath, - ContinueCoasting - }; - const PathConfigStorage configs_storage_; //!< The line configs for this layer for each feature type const coord_t z_; coord_t final_travel_z_; bool mode_skip_agressive_merge_; //!< Whether to give every new path the 'skip_agressive_merge_hint' property (see GCodePath); default is false. private: + // Indicates how coasting should be processed on the given path. + enum class ApplyCoasting + { + NoCoasting, // Do not apply coasting on this path, extrude it normally + CoastEntirePath, // Fully coast this path, i.e. replace it by travel moves + PartialCoasting // Extrude the first part of the path and coast the end + }; + + struct PathCoasting + { + ApplyCoasting apply_coasting{ ApplyCoasting::NoCoasting }; + size_t coasting_start_index{ 0 }; + Point3LL coasting_start_pos; + }; + const SliceDataStorage& storage_; //!< The polygon data obtained from FffPolygonProcessor const LayerIndex layer_nr_; //!< The layer number of this layer plan const bool is_initial_layer_; //!< Whether this is the first layer (which might be raft) @@ -699,28 +706,6 @@ class LayerPlan : public NoCopy */ bool makeRetractSwitchRetract(unsigned int extruder_plan_idx, unsigned int path_idx); - /*! - * Writes a path to GCode and performs coasting, or returns false if it did nothing. - * - * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. - * - * \param gcode The gcode to write the planned paths to. - * \param extruder_plan_idx The index of the current extruder plan. - * \param path_idx The index into LayerPlan::paths for the next path to be - * written to GCode. - * \param layer_thickness The height of the current layer. - * \param insertTempOnTime A function that inserts temperature changes at a given time. - * \param coasting_adjust Paths can be split up, so we need to know when to continue coasting from last, or even coast the entire path. - * \return Whether any GCode has been written for the path. - */ - bool writePathWithCoasting( - GCodeExport& gcode, - const size_t extruder_plan_idx, - const size_t path_idx, - const coord_t layer_thickness, - const std::function insertTempOnTime, - const std::pair coasting_adjust); - /*! * Applying speed corrections for minimal layer times and determine the fanSpeed. * @@ -895,6 +880,37 @@ class LayerPlan : public NoCopy const coord_t decelerate_length, const bool is_scarf_closure); + /*! + * Pre-calculates the coasting to be applied on the paths + * + * \param extruder_settings The current extruder settings + * \param paths The current set of paths to be written to GCode + * \param current_position The last position set in the gcode writer + * \return The list of coasting settings to be applied on the paths. It will always have the same size as paths. + */ + std::vector calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const; + + /*! + * Writes a path to GCode and performs coasting, or returns false if it did nothing. + * + * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. + * + * \param gcode The gcode to write the planned paths to. + * \param extruder_plan_idx The index of the current extruder plan. + * \param path_idx The index into LayerPlan::paths for the next path to be + * written to GCode. + * \param layer_thickness The height of the current layer. + * \param insertTempOnTime A function that inserts temperature changes at a given time. + * \param path_coasting The actual coasting to be applied to the path. + * \return Whether any GCode has been written for the path. + */ + bool writePathWithCoasting( + GCodeExport& gcode, + const size_t extruder_plan_idx, + const size_t path_idx, + const std::function insertTempOnTime, + const PathCoasting& path_coasting); + /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment * \param wall The currently processed wall diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index 0e4b62ed7b..604e93af3c 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -192,6 +192,11 @@ inline Point3LL operator*(const T i, const Point3LL& rhs) return rhs * i; } +inline Point3LL lerp(const Point3LL& a, const Point3LL& b, const double t) +{ + return Point3LL(cura::lerp(a.x_, b.x_, t), cura::lerp(a.y_, b.y_, t), cura::lerp(a.z_, b.z_, t)); +} + } // namespace cura diff --git a/include/utils/Coord_t.h b/include/utils/Coord_t.h index bd8f3e088b..8043bbd875 100644 --- a/include/utils/Coord_t.h +++ b/include/utils/Coord_t.h @@ -6,8 +6,11 @@ // Include Clipper to get the ClipperLib::IntPoint definition, which we reuse as Point definition. +#include #include +#include "utils/types/generic.h" + namespace cura { @@ -16,16 +19,22 @@ using coord_t = ClipperLib::cInt; static inline coord_t operator"" _mu(unsigned long long i) { return static_cast(i); -}; +} #define INT2MM(n) (static_cast(n) / 1000.0) #define INT2MM2(n) (static_cast(n) / 1000000.0) -#define MM2INT(n) (static_cast((n)*1000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM2_2INT(n) (static_cast((n)*1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM3_2INT(n) (static_cast((n)*1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2INT(n) (static_cast((n) * 1000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2_2INT(n) (static_cast((n) * 1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM3_2INT(n) (static_cast((n) * 1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) #define INT2MICRON(n) ((n) / 1) -#define MICRON2INT(n) ((n)*1) +#define MICRON2INT(n) ((n) * 1) + +template +[[nodiscard]] inline coord_t lerp(coord_t a, coord_t b, FactorType t) +{ + return std::llrint(std::lerp(static_cast(a), static_cast(b), t)); +} } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..d560af1fc0 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1227,6 +1227,109 @@ void LayerPlan::addSplitWall( } } +std::vector + LayerPlan::calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const +{ + std::vector path_coastings; + path_coastings.resize(paths.size()); + + if (extruder_settings.get("coasting_enable")) + { + // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). + // Note that this doesn't perform the coasting itself, it just calculates the coasting values which will be applied by the 'writePathWithCoasting' func. + // All of this is necessary since we split up paths because of scarf and acceleration-adjustments (start/end), so we need to have adjacency info. + + const double coasting_volume = extruder_settings.get("coasting_volume"); + const double coasting_min_volume = extruder_settings.get("coasting_min_volume"); + + for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse + | ranges::views::chunk_by( + [](const auto& path_a, const auto& path_b) + { + return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); + })) + { + double accumulated_volume = 0.0; + bool chunk_coasting_point_reached = false; + bool chunk_min_volume_reached = false; + + for (const auto& [path_idx, path] : reversed_chunk) + { + if (path.isTravelPath()) + { + continue; + } + + PathCoasting& path_coasting = path_coastings[path_idx]; + const double path_extrusion_per_length = path.config.extrusion_mm3_per_mm * path.flow; + + for (const auto& [point_idx, point] : path.points | ranges::views::enumerate | ranges::views::reverse) + { + Point3LL previous_point; + if (point_idx > 0) + { + previous_point = path.points[point_idx - 1]; + } + else if (path_idx > 0) + { + previous_point = paths[path_idx - 1].points.back(); + } + else + { + previous_point = current_position; + } + + const double segment_length = INT2MM((point - previous_point).vSize()); + const double segment_volume = segment_length * path_extrusion_per_length; + + accumulated_volume += segment_volume; + + if (! chunk_coasting_point_reached && path_coasting.apply_coasting == ApplyCoasting::NoCoasting) + { + if (accumulated_volume >= coasting_volume) + { + const double start_pos_factor = (accumulated_volume - coasting_volume) / segment_volume; + Point3LL coasting_start_pos = cura::lerp(previous_point, point, start_pos_factor); + path_coasting = { ApplyCoasting::PartialCoasting, point_idx, coasting_start_pos }; + chunk_coasting_point_reached = true; + } + else if (point_idx == 0) + { + path_coasting.apply_coasting = ApplyCoasting::CoastEntirePath; + } + } + + if (accumulated_volume >= coasting_min_volume) + { + chunk_min_volume_reached = true; + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) + { + break; + } + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) + { + break; + } + } + + if (! chunk_min_volume_reached) + { + // It turns out this chunk doesn't fit the minimum requirements for coasting, so skip it + for (const auto& [path_idx, path] : reversed_chunk) + { + path_coastings[path_idx].apply_coasting = ApplyCoasting::NoCoasting; + } + } + } + } + + return path_coastings; +} + coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const { coord_t distance_to_bridge_start = 0; @@ -2054,7 +2157,7 @@ double ExtruderPlan::getRetractTime(const GCodePath& path) return retraction_config_.distance / (path.retract ? retraction_config_.speed : retraction_config_.primeSpeed); } -std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) +std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const { const double length = (p0 - p1).vSizeMM(); return { length, length / (path.config.getSpeed() * path.speed_factor) }; @@ -2369,57 +2472,9 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); }; - std::vector> coasting_adjust_per_path; - if (extruder.settings_.get("coasting_enable")) - { - // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). - // Note that this doesn't perform the coasting itself, it just calculates the 'adjust coasting' vector needed by the 'writePathWithCoasting' func. - // All of this is nescesary since we split up paths because of scarf and accelleration-adjustments (start/end), so we need to have adjacency info. - - const double coasting_volume = extruder.settings_.get("coasting_volume"); - coasting_adjust_per_path.assign(paths.size(), { AdjustCoasting::AsNormal, coasting_volume }); - - for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse - | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) - { - return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); - })) - { - double coasting_left = coasting_volume; - for (const auto& [path_idx, path] : reversed_chunk) - { - if (path.isTravelPath()) - { - break; - } - - coord_t accumulated_length = 0; - for (size_t i_pt = 1; i_pt < path.points.size(); i_pt++) - { - accumulated_length += (path.points[i_pt - 1] - path.points[i_pt]).vSize(); - } - const double path_volume = INT2MM2(INT2MM(accumulated_length * path.config.getLineWidth()) * layer_thickness_); + const std::vector coasting_per_path = calculatePathsCoasting(extruder.settings_, paths, gcode.getPosition()); - if (path_volume < coasting_left) - { - coasting_adjust_per_path[path_idx].first = AdjustCoasting::CoastEntirePath; - } - else - { - coasting_adjust_per_path[path_idx] = { AdjustCoasting::ContinueCoasting, coasting_left }; - } - - coasting_left -= path_volume; - if (coasting_left <= 0) - { - break; - } - } - } - } - - for (int64_t path_idx = 0; path_idx < paths.size(); path_idx++) + for (size_t path_idx = 0; path_idx < paths.size(); path_idx++) { extruder_plan.handleInserts(path_idx, gcode); cumulative_path_time = 0.; // reset to 0 for current path. @@ -2612,7 +2667,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool coasting = extruder.settings_.get("coasting_enable"); if (coasting) { - coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness_, insertTempOnTime, coasting_adjust_per_path[path_idx]); + coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, insertTempOnTime, coasting_per_path[path_idx]); } if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm @@ -2769,133 +2824,58 @@ bool LayerPlan::writePathWithCoasting( GCodeExport& gcode, const size_t extruder_plan_idx, const size_t path_idx, - const coord_t layer_thickness, const std::function insertTempOnTime, - const std::pair coasting_adjust) + const PathCoasting& path_coasting) { - ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; - const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - const double coasting_volume = std::min(extruder.settings_.get("coasting_volume"), coasting_adjust.second); - if (coasting_volume <= 0) + if (path_coasting.apply_coasting == ApplyCoasting::NoCoasting) { return false; } + + size_t coasting_start_index; + Point3LL previous_position = gcode.getPosition(); + const ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; const std::vector& paths = extruder_plan.paths_; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! (paths[path_idx + 1].config.isTravelPath() || coasting_adjust.first == AdjustCoasting::ContinueCoasting)) - || path.points.size() < 2) - { - return false; - } - - coord_t coasting_min_dist_considered = MM2INT(0.1); // hardcoded setting for when to not perform coasting - - const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; - - coord_t coasting_dist = coasting_adjust.first == AdjustCoasting::CoastEntirePath - ? std::numeric_limits::max() / 2 - : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); - const coord_t coasting_min_dist - = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - // /\ the minimal distance when coasting will coast the full coasting volume instead of linearly less with linearly smaller paths - - std::vector accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...) - accumulated_dist_per_point.push_back(0); - - coord_t accumulated_dist = 0; - - bool length_is_less_than_min_dist = true; - - std::optional acc_dist_idx_gt_coast_dist; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point) - // == the point printed BEFORE the start point for coasting - - const Point3LL* last = &path.points[path.points.size() - 1]; - for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++) - { - const Point3LL& point = path.points[path.points.size() - 1 - backward_point_idx]; - const coord_t distance = (point - *last).vSize(); - accumulated_dist += distance; - accumulated_dist_per_point.push_back(accumulated_dist); - - if (! acc_dist_idx_gt_coast_dist.has_value() && accumulated_dist >= coasting_dist) - { - acc_dist_idx_gt_coast_dist = backward_point_idx; // the newly added point - } - - if (accumulated_dist >= coasting_min_dist) - { - length_is_less_than_min_dist = false; - break; - } - - last = &point; - } - coasting_dist = std::min(coasting_dist, accumulated_dist); // if the path is shorter than coasting_dist, we should coast the whole path + const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - if (accumulated_dist < coasting_min_dist_considered) + if (path_coasting.apply_coasting == ApplyCoasting::CoastEntirePath) { - return false; + coasting_start_index = 0; } - coord_t actual_coasting_dist = coasting_dist; - if (length_is_less_than_min_dist) + else { - // in this case accumulated_dist is the length of the whole path - actual_coasting_dist = accumulated_dist * coasting_dist / coasting_min_dist; - if (actual_coasting_dist == 0) // Downscaling due to Minimum Coasting Distance reduces coasting to less than 1 micron. - { - return false; // Skip coasting at all then. - } - for (acc_dist_idx_gt_coast_dist = 1; acc_dist_idx_gt_coast_dist.value() < accumulated_dist_per_point.size(); acc_dist_idx_gt_coast_dist.value()++) - { // search for the correct coast_dist_idx - if (accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value()] >= actual_coasting_dist) - { - break; - } - } - } - - assert( - acc_dist_idx_gt_coast_dist.has_value() && acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size()); // something has gone wrong; coasting_min_dist < coasting_dist ? - - const size_t point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist.value(); + const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; + coasting_start_index = path_coasting.coasting_start_index; - Point3LL start; - { // computation of begin point of coasting - const coord_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value() - 1]; - const Point3LL& a = path.points[point_idx_before_start]; - const Point3LL& b = path.points[point_idx_before_start + 1]; - start = b + (a - b).resized(residual_dist); - } - - Point3LL prev_pt = gcode.getPositionXY(); - { // write normal extrude path: - for (size_t point_idx = 0; point_idx <= point_idx_before_start; point_idx++) + // write normal extrude path, followed by split extrusion to coasting starting point + for (size_t point_idx = 0; point_idx < coasting_start_index; point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, path.points[point_idx], extrude_speed); - prev_pt = path.points[point_idx]; + previous_position = path.points[point_idx]; } - gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); - sendLineTo(path, start, extrude_speed); + gcode.writeExtrusion(path_coasting.coasting_start_pos, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, path_coasting.coasting_start_pos, extrude_speed); } // write coasting path - for (size_t point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++) + for (size_t point_idx = coasting_start_index; point_idx < path.points.size(); point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); const Ratio coasting_speed_modifier = extruder.settings_.get("coasting_speed"); const Velocity speed = Velocity(coasting_speed_modifier * path.config.getSpeed()); writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); - prev_pt = path.points[point_idx]; + previous_position = path.points[point_idx]; } + return true; } diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index e94c988b78..32eee9733f 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -417,7 +417,7 @@ const Point3LL& GCodeExport::getPosition() const } Point2LL GCodeExport::getPositionXY() const { - return Point2LL(current_position_.x_, current_position_.y_); + return current_position_.toPoint2LL(); } int GCodeExport::getPositionZ() const From f37622470e576687c1a06b3db42b58da6d02f89b Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 23 Oct 2024 14:37:48 +0000 Subject: [PATCH 086/139] Applied clang-format. --- include/utils/Coord_t.h | 8 ++++---- src/LayerPlan.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/utils/Coord_t.h b/include/utils/Coord_t.h index 8043bbd875..ac2e4741af 100644 --- a/include/utils/Coord_t.h +++ b/include/utils/Coord_t.h @@ -23,12 +23,12 @@ static inline coord_t operator"" _mu(unsigned long long i) #define INT2MM(n) (static_cast(n) / 1000.0) #define INT2MM2(n) (static_cast(n) / 1000000.0) -#define MM2INT(n) (static_cast((n) * 1000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM2_2INT(n) (static_cast((n) * 1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM3_2INT(n) (static_cast((n) * 1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2INT(n) (static_cast((n)*1000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2_2INT(n) (static_cast((n)*1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM3_2INT(n) (static_cast((n)*1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) #define INT2MICRON(n) ((n) / 1) -#define MICRON2INT(n) ((n) * 1) +#define MICRON2INT(n) ((n)*1) template [[nodiscard]] inline coord_t lerp(coord_t a, coord_t b, FactorType t) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index d560af1fc0..232bad63e5 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 3f771a788b92b9ce3a437d90e5207907ab0fe320 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 24 Oct 2024 10:28:12 +0200 Subject: [PATCH 087/139] Fix thin strips of infill missing. For a previous fix (see ticket CURA-11597 for the support issues fixed --doubling up lines of support-- and the related github discussion Cura/issues/18924 for the infill issues fixed --extra dots of infill at the edges--) we did some morpholigical operations to attempt to make the output more smooth. This caused more problems than its worth, and was in fact already removed in the original PR w.r.t. support (see commit eeec9636f7b3f749b790a1bea4c2901196cee027 ) -- seems we (read I) forgot to remove it for the infill case as well. part of CURA-12068 (or a belated contribution to CURA-11597). --- src/skin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skin.cpp b/src/skin.cpp index 351290be93..9d3f56db2c 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -530,7 +530,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); const Shape more_dense_infill = infill_area.difference(less_dense_infill); infill_area_per_combine_current_density.push_back( - simplifier.polygon(more_dense_infill.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + simplifier.polygon(more_dense_infill.difference(sum_more_dense))); if (is_connected) { sum_more_dense = sum_more_dense.unionPolygons(more_dense_infill); @@ -538,7 +538,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) } part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); - infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense))); part.infill_area_own = std::nullopt; // clear infill_area_own, it's not needed any more. assert(! part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is now initialized"); } From c0d91c9a41c68487fca32d44184ca4b03838aca2 Mon Sep 17 00:00:00 2001 From: rburema Date: Thu, 24 Oct 2024 08:32:56 +0000 Subject: [PATCH 088/139] Applied clang-format. --- src/TreeSupport.cpp | 8 ++++---- src/skin.cpp | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 8268bc2e9c..77e7acdd19 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2037,10 +2037,11 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); - if(hole.size() > 1) + if (hole.size() > 1) { // The hole contains other branches! It can not be fully removed. - // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring larger changes. + // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring + // larger changes. holes_resting_outside[layer_idx].emplace(idx); } else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) @@ -2057,8 +2058,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { // TODO should technically be outline: Check if this is fine either way as it would save an offset - if (hole_aabb.hit(AABB(hole2)) - && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) + if (hole_aabb.hit(AABB(hole2)) && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) { hole_rest_map[layer_idx][idx].emplace_back(idx2); } diff --git a/src/skin.cpp b/src/skin.cpp index 9d3f56db2c..894692c815 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -529,8 +529,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); const Shape more_dense_infill = infill_area.difference(less_dense_infill); - infill_area_per_combine_current_density.push_back( - simplifier.polygon(more_dense_infill.difference(sum_more_dense))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(more_dense_infill.difference(sum_more_dense))); if (is_connected) { sum_more_dense = sum_more_dense.unionPolygons(more_dense_infill); From ebcd20a02d04e5f4a36fc30fabbca45d023d5b6f Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 25 Oct 2024 08:35:05 +0200 Subject: [PATCH 089/139] Add comment as suggested by review CURA-12175 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 232bad63e5..1cc3656ae7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -1293,7 +1293,7 @@ std::vector path_coasting = { ApplyCoasting::PartialCoasting, point_idx, coasting_start_pos }; chunk_coasting_point_reached = true; } - else if (point_idx == 0) + else if (point_idx == 0) // End of path reached (reverse iteration), coasting not fulfilled { path_coasting.apply_coasting = ApplyCoasting::CoastEntirePath; } From b92e174d3628a29d27647b2db3f2278dc475bd00 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 25 Oct 2024 06:35:54 +0000 Subject: [PATCH 090/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1cc3656ae7..56d96c0e2a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 893ad91048c0cf43972d5c9c26798697b785dda9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 28 Oct 2024 14:42:58 +0100 Subject: [PATCH 091/139] Apply scarf seam and speed gradient in surface mode CURA-12176 The addSplitWall method is now a template so that it can handle either ExtrusionLine or Polygon objects. It also takes a function as parameter because the way we add extrusion lines is not the same for surface mode and normal mode. In order to simplify this function, I added a PathAdapter class to work with different types of paths with more transparency. --- CMakeLists.txt | 1 + include/LayerPlan.h | 88 +++++++++++--- src/FffGcodeWriter.cpp | 46 +++++-- src/LayerPlan.cpp | 269 +++++++++++++++++++++++++++++------------ src/TopSurface.cpp | 2 +- 5 files changed, 297 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index daf6022fbb..47581ceca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(engine_SRCS # Except main.cpp. src/Mold.cpp src/multiVolumes.cpp src/path_ordering.cpp + src/PathAdapter.cpp src/Preheat.cpp src/PrimeTower/PrimeTower.cpp src/PrimeTower/PrimeTowerNormal.cpp diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0be204faf2..b2b207d3be 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -38,6 +38,9 @@ class Comb; class SliceDataStorage; class LayerPlanBuffer; +template +class PathAdapter; + /*! * The LayerPlan class stores multiple moves that are planned. * @@ -398,11 +401,15 @@ class LayerPlan : public NoCopy const Polygon& polygon, int startIdx, const bool reverse, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio& flow_ratio = 1.0_r, - bool always_retract = false); + bool always_retract = false, + bool scarf_seam = false, + bool smooth_speed = false, + bool is_candidate_small_feature = false); /*! * Add polygons to the gcode with optimized order. @@ -435,13 +442,16 @@ class LayerPlan : public NoCopy void addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, - const std::optional start_near_location = std::optional()); + const std::optional start_near_location = std::optional(), + bool scarf_seam = false, + bool smooth_acceleration = false); /*! * Add a single line that is part of a wall to the gcode. @@ -836,18 +846,15 @@ class LayerPlan : public NoCopy /*! * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline * \param wall The full wall to be added * \param wall_length The pre-calculated full wall length * \param start_idx The index of the point where to start printing the wall * \param direction The direction along which to print the wall, which should be 1 or -1 * \param max_index The last index to be used when iterating over the wall segments - * \param settings The settings which should apply to this wall added to the layer plan * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air - * \param roofing_config The config with which to print the wall lines that are exposed to air - * \param bridge_config The config with which to print the wall lines that are spanning a bridge * \param flow_ratio The ratio with which to multiply the extrusion amount - * \param line_width_ratio The line width ratio to be applied - * \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 lin + * \param nominal_line_width The nominal line width for the wall * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) * \param is_small_feature Indicates whether the wall is so small that it should be processed differently @@ -864,26 +871,26 @@ class LayerPlan : public NoCopy * \param end_speed_ratio The ratio of the top speed to be applied when finishing a segment * \param decelerate_length The pre-calculated length of the deceleration phase * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or the normal first pass of the wall + * \param compute_distance_to_bridge_start Whether we should compute the distance to start of bridge. This is + * possible only if PathType is ExtrusionLine and will be ignored otherwise. + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters */ + template void addSplitWall( - const ExtrusionLine& wall, + const PathAdapter& wall, const coord_t wall_length, - size_t start_idx, - const int direction, + const size_t start_idx, const size_t max_index, - const Settings& settings, + const int direction, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - const double flow_ratio, - const Ratio line_width_ratio, - double& non_bridge_line_volume, - const coord_t min_bridge_line_len, const bool always_retract, const bool is_small_feature, Ratio small_feature_speed_factor, const coord_t max_area_deviation, const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, const auto scarf_seam_length, const auto scarf_seam_start_ratio, const auto scarf_split_distance, @@ -893,7 +900,52 @@ class LayerPlan : public NoCopy const coord_t accelerate_length, const Ratio end_speed_ratio, const coord_t decelerate_length, - const bool is_scarf_closure); + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const std::function& func_add_segment); + + /*! + * \brief Add a wall to the gcode with optimized order, possibly adding a scarf seam / speed gradient according to settings + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param wall The full wall to be added + * \param start_idx The index of the point where to start printing the wall + * \param settings The settings which should apply to this wall added to the layer plan + * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air + * \param flow_ratio The ratio with which to multiply the extrusion amount + * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param is_closed Indicates whether the path is closed (or open) + * \param is_reversed Indicates if the path is to be processed backwards + * \param is_candidate_small_feature Indicates whether the path should be tested for being treated as a smell feature + * \param scarf_seam Indicates whether we may set a scraf seam to the path + * \param smooth_speed Indicates whether we may set a speed gradient to the path + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + */ + template + void addWallWithScarfSeam( + const PathAdapter& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_candidate_small_feature, + const bool scarf_seam, + const bool smooth_speed, + const std::function& func_add_segment); /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index fcba3b052b..a8642cf95c 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -735,6 +735,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_base_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -895,6 +896,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_interface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1073,6 +1075,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_surface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1477,13 +1480,14 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { LayerIndex layer_nr = std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr()); - if (layer_nr == 0 && Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("adhesion_type") == EPlatformAdhesion::BRIM) + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; + if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::BRIM) { return; // ooze shield already generated by brim } if (storage.ooze_shield.size() > 0 && layer_nr < storage.ooze_shield.size()) { - gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } } @@ -1516,7 +1520,7 @@ void FffGcodeWriter::processDraftShield(const SliceDataStorage& storage, LayerPl } } - gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& storage) @@ -1723,7 +1727,26 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); const bool spiralize = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize"); - gcode_layer.addPolygonsByOptimizer(polygons, mesh_config.inset0_config, z_seam_config, mesh.settings.get("wall_0_wipe_dist"), spiralize); + constexpr Ratio flow_ratio = 1.0; + constexpr bool always_retract = false; + constexpr bool reverse_order = false; + constexpr std::optional start_near_location = std::nullopt; + constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; + + gcode_layer.addPolygonsByOptimizer( + polygons, + mesh_config.inset0_config, + mesh.settings, + z_seam_config, + mesh.settings.get("wall_0_wipe_dist"), + spiralize, + flow_ratio, + always_retract, + reverse_order, + start_near_location, + scarf_seam, + smooth_speed); addMeshOpenPolyLinesToGCode(mesh, mesh_config, gcode_layer); } @@ -1966,7 +1989,7 @@ bool FffGcodeWriter::processMultiLayerInfill( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(infill_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx]); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx], mesh.settings); } if (! infill_lines.empty()) @@ -2723,7 +2746,7 @@ bool FffGcodeWriter::processSingleLayerInfill( constexpr bool force_comb_retract = false; // start the infill polygons at the nearest vertex to the current location gcode_layer.addTravel(PolygonUtils::findNearestVert(gcode_layer.getLastPlannedPositionOrStartingPosition(), infill_polygons).p(), force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], mesh.settings, ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); } const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC @@ -2965,7 +2988,7 @@ bool FffGcodeWriter::processInsets( gcode_layer.addTravel(spiral_inset[spiral_start_vertex]); } int wall_0_wipe_dist(0); - gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, ZSeamConfig(), wall_0_wipe_dist); + gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, mesh.settings, ZSeamConfig(), wall_0_wipe_dist); } } // for non-spiralized layers, determine the shape of the unsupported areas below this part @@ -3596,7 +3619,7 @@ void FffGcodeWriter::processSkinPrintFeature( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(skin_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(skin_polygons, config); + gcode_layer.addPolygonsByOptimizer(skin_polygons, config, mesh.settings); } if (monotonic) @@ -4001,6 +4024,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer gcode_layer.addPolygonsByOptimizer( support_polygons, configs[combine_idx], + mesh_group_settings, z_seam_config, wall_0_wipe_dist, spiralize, @@ -4167,13 +4191,13 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support if (gcode_layer.getLayerNr() == 0) { - gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config, roof_extruder.settings_); } if (! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config, roof_extruder.settings_); } if (! roof_paths.empty()) { @@ -4287,7 +4311,7 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L { constexpr bool force_comb_retract = false; gcode_layer.addTravel(bottom_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config); + gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config, bottom_extruder.settings_); } if (! bottom_paths.empty()) { diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..c7467bf5fe 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -14,6 +14,7 @@ #include "Application.h" //To communicate layer view data. #include "ExtruderTrain.h" +#include "PathAdapter.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. #include "Slice.h" #include "WipeScriptConfig.h" @@ -557,29 +558,54 @@ void LayerPlan::addPolygon( const Polygon& polygon, int start_idx, const bool backwards, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio& flow_ratio, - bool always_retract) + bool always_retract, + bool scarf_seam, + bool smooth_speed, + bool is_candidate_small_feature) { - constexpr Ratio width_ratio = 1.0_r; // Not printed with variable line width. + constexpr bool is_closed = true; + Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); - const int direction = backwards ? -1 : 1; - for (size_t point_idx = 1; point_idx < polygon.size(); point_idx++) - { - Point2LL p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()]; - addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - p0 = p1; - } + + addWallWithScarfSeam( + PathAdapter(polygon, config.getLineWidth()), + start_idx, + settings, + config, + flow_ratio, + always_retract, + is_closed, + backwards, + is_candidate_small_feature, + scarf_seam, + smooth_speed, + [this, &config, &spiralize]( + const Point3LL& /*start*/, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t /*distance_to_bridge_start*/) + { + constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; + constexpr bool travel_to_z = false; + + addExtrusionMove(end, config, SpaceFillType::Polygons, actual_flow_ratio, line_width_ratio, spiralize, speed_factor, fan_speed, travel_to_z); + }); + + if (polygon.size() > 2) { - addExtrusionMove(polygon[start_idx], config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - if (wall_0_wipe_dist > 0) { // apply outer wall wipe p0 = polygon[start_idx]; + const int direction = backwards ? -1 : 1; int distance_traversed = 0; for (size_t point_idx = 1;; point_idx++) { @@ -611,13 +637,16 @@ void LayerPlan::addPolygon( void LayerPlan::addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, const ZSeamConfig& z_seam_config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio flow_ratio, bool always_retract, bool reverse_order, - const std::optional start_near_location) + const std::optional start_near_location, + bool scarf_seam, + bool smooth_speed) { if (polygons.empty()) { @@ -630,20 +659,33 @@ void LayerPlan::addPolygonsByOptimizer( } orderOptimizer.optimize(); - if (! reverse_order) + auto add_polygons + = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &always_retract, &scarf_seam, &smooth_speed](const auto& iterator_begin, const auto& iterator_end) { - for (const PathOrdering& path : orderOptimizer.paths_) + for (auto iterator = iterator_begin; iterator != iterator_end; ++iterator) { - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); + addPolygon( + *iterator->vertices_, + iterator->start_vertex_, + iterator->backwards_, + settings, + config, + wall_0_wipe_dist, + spiralize, + flow_ratio, + always_retract, + scarf_seam, + smooth_speed); } + }; + + if (! reverse_order) + { + add_polygons(orderOptimizer.paths_.begin(), orderOptimizer.paths_.end()); } else { - for (int index = orderOptimizer.paths_.size() - 1; index >= 0; --index) - { - const PathOrdering& path = orderOptimizer.paths_[index]; - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); - } + add_polygons(orderOptimizer.paths_.rbegin(), orderOptimizer.paths_.rend()); } } @@ -865,7 +907,7 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : 1.0_r, + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -996,25 +1038,22 @@ void LayerPlan::addWall( addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } +template void LayerPlan::addSplitWall( - const ExtrusionLine& wall, + const PathAdapter& wall, const coord_t wall_length, - size_t start_idx, - const int direction, + const size_t start_idx, const size_t max_index, - const Settings& settings, + const int direction, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - const double flow_ratio, - const Ratio line_width_ratio, - double& non_bridge_line_volume, - const coord_t min_bridge_line_len, const bool always_retract, const bool is_small_feature, Ratio small_feature_speed_factor, const coord_t max_area_deviation, const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, const auto scarf_seam_length, const auto scarf_seam_start_ratio, const auto scarf_split_distance, @@ -1024,13 +1063,22 @@ void LayerPlan::addSplitWall( const coord_t accelerate_length, const Ratio end_speed_ratio, const coord_t decelerate_length, - const bool is_scarf_closure) + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const std::function& func_add_segment) { coord_t distance_to_bridge_start = 0; // will be updated before each line is processed - ExtrusionJunction p0 = wall[start_idx]; + Point2LL p0 = wall.pointAt(start_idx); + coord_t w0 = wall.lineWidthAt(start_idx); bool first_line = ! is_scarf_closure; bool first_split = ! is_scarf_closure; - Point3LL split_origin = p0.p_; + Point3LL split_origin = p0; if (! is_scarf_closure && scarf_seam_length > 0) { split_origin.z_ = scarf_max_z_offset; @@ -1044,16 +1092,21 @@ void LayerPlan::addSplitWall( for (size_t point_idx = 1; point_idx < max_index; point_idx++) { - const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; + const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); + const Point2LL& p1 = wall.pointAt(actual_point_index); + const coord_t w1 = wall.lineWidthAt(actual_point_index); - if (! bridge_wall_mask_.empty()) + if constexpr (std::is_same_v) { - distance_to_bridge_start = computeDistanceToBridgeStart(wall, (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); + if (compute_distance_to_bridge_start && ! bridge_wall_mask_.empty()) + { + distance_to_bridge_start = computeDistanceToBridgeStart(wall.getPath(), (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); + } } if (first_line) { - addTravel(p0.p_, always_retract); + addTravel(p0, always_retract); first_line = false; } @@ -1069,8 +1122,8 @@ void LayerPlan::addSplitWall( pieces we'd want to get low enough deviation, then check if each piece is not too short at the end. */ - const coord_t delta_line_width = p1.w_ - p0.w_; - const Point2LL line_vector = p1.p_ - p0.p_; + const coord_t delta_line_width = w1 - w0; + const Point2LL line_vector = p1 - p0; const coord_t line_length = vSize(line_vector); /* Calculate how much the line would deviate from the trapezoidal shape if printed at average width. @@ -1091,12 +1144,13 @@ void LayerPlan::addSplitWall( const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) - const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); - const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); + const coord_t line_width = std::lrint(static_cast(w0) + average_progress * static_cast(delta_line_width)); + const Ratio line_width_ratio = static_cast(line_width) / nominal_line_width; + const Point2LL destination = p0 + normal(line_vector, piece_length * (piece + 1)); if (is_small_feature && ! is_scarf_closure) { constexpr bool spiralize = false; - addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width * line_width_ratio, spiralize, small_feature_speed_factor); + addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width_ratio, spiralize, small_feature_speed_factor); } else { @@ -1170,7 +1224,7 @@ void LayerPlan::addSplitWall( if (first_split) { // Manually add a Z-only travel move to set the nozzle at the height of the first point - addTravel(p0.p_, always_retract, split_origin.z_); + addTravel(p0, always_retract, split_origin.z_); first_split = false; } } @@ -1198,20 +1252,13 @@ void LayerPlan::addSplitWall( } // now add the (sub-)segment - constexpr bool travel_to_z = false; - addWallLine( + func_add_segment( split_origin, split_destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio * scarf_segment_flow_ratio, - line_width * line_width_ratio, - non_bridge_line_volume, accelerate_speed_factor * decelerate_speed_factor, - distance_to_bridge_start, - travel_to_z); + flow_ratio * scarf_segment_flow_ratio, + line_width_ratio, + distance_to_bridge_start); wall_processed_distance = destination_position; piece_remaining_distance -= length_to_process; @@ -1308,39 +1355,41 @@ coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const return distance_to_bridge_start; } -void LayerPlan::addWall( - const ExtrusionLine& wall, +template +void LayerPlan::addWallWithScarfSeam( + const PathAdapter& wall, size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - coord_t wall_0_wipe_dist, const double flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, - const bool is_linked_path, + const bool is_candidate_small_feature, const bool scarf_seam, - const bool smooth_speed) + const bool smooth_speed, + const std::function& func_add_segment) { if (wall.empty()) { return; } - const bool actual_scarf_seam = scarf_seam && is_closed; - double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output + const bool actual_scarf_seam = scarf_seam && is_closed; const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); - const Ratio nominal_line_width_multiplier{ - 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } - }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + const coord_t nominal_line_width = default_config.getLineWidth(); const coord_t wall_length = wall.length(); const coord_t small_feature_max_length = settings.get("small_feature_max_length"); - const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall_length < small_feature_max_length; + const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || is_candidate_small_feature) && wall_length < small_feature_max_length; const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); @@ -1368,25 +1417,23 @@ void LayerPlan::addWall( auto addSplitWallPass = [&](bool is_scarf_closure) { + constexpr bool compute_distance_to_bridge_start = true; + addSplitWall( - wall, + PathAdapter(wall), wall_length, start_idx, - direction, max_index, - settings, + direction, default_config, - roofing_config, - bridge_config, - flow_ratio, - nominal_line_width_multiplier, - non_bridge_line_volume, - min_bridge_line_len, always_retract, is_small_feature, small_feature_speed_factor, max_area_deviation, max_resolution, + flow_ratio, + nominal_line_width, + min_bridge_line_len, layer_nr_ > 0 ? scarf_seam_length : 0, scarf_seam_start_ratio, scarf_split_distance, @@ -1396,7 +1443,9 @@ void LayerPlan::addWall( accelerate_length, end_speed_ratio, decelerate_length, - is_scarf_closure); + is_scarf_closure, + compute_distance_to_bridge_start, + func_add_segment); }; // First pass to add the wall with the scarf beginning and acceleration @@ -1407,6 +1456,68 @@ void LayerPlan::addWall( // Second pass to add the scarf closure addSplitWallPass(true); } +} + +void LayerPlan::addWall( + const ExtrusionLine& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const GCodePathConfig& roofing_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path, + const bool scarf_seam, + const bool smooth_speed) +{ + if (wall.empty()) + { + return; + } + + double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output + const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + + addWallWithScarfSeam( + PathAdapter(wall), + start_idx, + settings, + default_config, + flow_ratio, + always_retract, + is_closed, + is_reversed, + wall.inset_idx_ == 0, + scarf_seam, + smooth_speed, + [this, &settings, &default_config, &roofing_config, &bridge_config, &non_bridge_line_volume]( + const Point3LL& start, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t distance_to_bridge_start) + { + constexpr bool travel_to_z = false; + + addWallLine( + start, + end, + settings, + default_config, + roofing_config, + bridge_config, + actual_flow_ratio, + line_width_ratio, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start, + travel_to_z); + }); if (wall.size() >= 2) { @@ -2381,7 +2492,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 15a6b03cf8..beb6154d3e 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -124,7 +124,7 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage { constexpr bool force_comb_retract = false; layer.addTravel(ironing_polygons[0][0], force_comb_retract); - layer.addPolygonsByOptimizer(ironing_polygons, line_config, ZSeamConfig()); + layer.addPolygonsByOptimizer(ironing_polygons, line_config, mesh.settings, ZSeamConfig()); added = true; } if (! ironing_lines.empty()) From ca5f958c9fec3a6394deb82429169b55fcf52c2c Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Mon, 28 Oct 2024 13:43:26 +0000 Subject: [PATCH 092/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c7467bf5fe..66723a14bf 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2492,7 +2492,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From d036c0bb08ddf0fe1c6e9c287ea321374d2a477a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 28 Oct 2024 15:04:46 +0100 Subject: [PATCH 093/139] Add missing PathAdapter files CURA-12176 --- include/PathAdapter.h | 63 +++++++++++++++++++++++++++++++++++++++++++ src/PathAdapter.cpp | 35 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 include/PathAdapter.h create mode 100644 src/PathAdapter.cpp diff --git a/include/PathAdapter.h b/include/PathAdapter.h new file mode 100644 index 0000000000..21a71ee1da --- /dev/null +++ b/include/PathAdapter.h @@ -0,0 +1,63 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PATH_ADAPTER_H +#define PATH_ADAPTER_H + +#include + +#include "geometry/Point2LL.h" +#include "utils/Coord_t.h" + +namespace cura +{ + +/* Adapter class to allow extrusion-related functions to work with either an ExtrusionLine or a Polygon */ +template +class PathAdapter +{ +public: + /*! + * \brief Base constructor + * \param path The actual stored path + * \param fixed_line_width The fixed line width in case the stored path doesn't handle information about a variable + * line width. Can be omitted otherwise. + */ + PathAdapter(const PathType& path, coord_t fixed_line_width = 0) + : path_(path) + , fixed_line_width_(fixed_line_width) + { + } + + bool empty() const + { + return path_.empty(); + } + + size_t size() const + { + return path_.size(); + } + + coord_t length() const + { + return path_.length(); + } + + const Point2LL& pointAt(size_t index) const; + + coord_t lineWidthAt(size_t index) const; + + const PathType& getPath() const + { + return path_; + } + +private: + const PathType& path_; + const coord_t fixed_line_width_; +}; + +} // namespace cura + +#endif // PATH_ADAPTER_H diff --git a/src/PathAdapter.cpp b/src/PathAdapter.cpp new file mode 100644 index 0000000000..09a5437493 --- /dev/null +++ b/src/PathAdapter.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PathAdapter.h" + +#include "utils/ExtrusionLine.h" + +namespace cura +{ + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.junctions_.at(index).p_; +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t index) const +{ + return path_.junctions_.at(index).w_; +} + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.at(index); +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t /*index*/) const +{ + return fixed_line_width_; +} + +} // namespace cura From 04262454d1560b98ae1f04a0694c26ac220f3b0c Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 10:57:26 +0100 Subject: [PATCH 094/139] Add writeGcodeFile function to GCodeExport Introduce a new method writeGcodeFile to the GCodeExport class, which ensures that the output stream is properly flushed. This function is also called at the end of the Gcode writing process to finalize the file. NP-470 --- include/gcodeExport.h | 2 ++ src/FffGcodeWriter.cpp | 1 + src/gcodeExport.cpp | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 7b211f8516..ee18936e39 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -409,6 +409,8 @@ class GCodeExport : public NoCopy */ bool needPrimeBlob() const; + void writeGcodeFile(); + private: /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index fcba3b052b..bed6ad7176 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -4437,6 +4437,7 @@ void FffGcodeWriter::finalize() } gcode.writeComment("End of Gcode"); + gcode.writeGcodeFile(); /* the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB. gcode.writeCode("M25 ;Stop reading from this point on."); diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index e94c988b78..c93a068115 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1726,9 +1726,14 @@ void GCodeExport::finalize(const char* endCode) for (int n = 1; n < MAX_EXTRUDERS; n++) if (getTotalFilamentUsed(n) > 0) spdlog::info("Filament {}: {}", n + 1, int(getTotalFilamentUsed(n))); +} + +void GCodeExport::writeGcodeFile() +{ output_stream_->flush(); } + double GCodeExport::getExtrudedVolumeAfterLastWipe(size_t extruder) { return eToMm3(extruder_attr_[extruder].last_e_value_after_wipe_, extruder); From b394b7bfcc9658045aaa18e5fcaa736e6d7efbae Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 10:59:44 +0100 Subject: [PATCH 095/139] Remove unnecessary blank line NP-470 --- src/gcodeExport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index c93a068115..b4ac8f1bdd 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1733,7 +1733,6 @@ void GCodeExport::writeGcodeFile() output_stream_->flush(); } - double GCodeExport::getExtrudedVolumeAfterLastWipe(size_t extruder) { return eToMm3(extruder_attr_[extruder].last_e_value_after_wipe_, extruder); From 3bb6d32ec62ce7cd9b67dd607fd32ef25ac0968d Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 11:51:02 +0100 Subject: [PATCH 096/139] Refactor writeGcodeFile to flushOutputStream Renamed the function writeGcodeFile to flushOutputStream for clarity. This change more accurately reflects the function's responsibility, which is to flush the output stream instead of managing file writing directly. Updated all references in the source files accordingly. NP-470 --- include/gcodeExport.h | 5 ++++- src/FffGcodeWriter.cpp | 2 +- src/gcodeExport.cpp | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/gcodeExport.h b/include/gcodeExport.h index ee18936e39..0dd81541b8 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -409,7 +409,10 @@ class GCodeExport : public NoCopy */ bool needPrimeBlob() const; - void writeGcodeFile(); + /* + * Function is used to write the content of output_stream to the gcode file + */ + void flushOutputStream(); private: /*! diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index bed6ad7176..2417723014 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -4437,7 +4437,7 @@ void FffGcodeWriter::finalize() } gcode.writeComment("End of Gcode"); - gcode.writeGcodeFile(); + gcode.flushOutputStream(); /* the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB. gcode.writeCode("M25 ;Stop reading from this point on."); diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index b4ac8f1bdd..f9749b0d6e 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1726,9 +1726,10 @@ void GCodeExport::finalize(const char* endCode) for (int n = 1; n < MAX_EXTRUDERS; n++) if (getTotalFilamentUsed(n) > 0) spdlog::info("Filament {}: {}", n + 1, int(getTotalFilamentUsed(n))); + flushOutputStream(); } -void GCodeExport::writeGcodeFile() +void GCodeExport::flushOutputStream() { output_stream_->flush(); } From 81d2ce014358a3dc930c71770e3ad7341ff13d8a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 13:27:05 +0100 Subject: [PATCH 097/139] Use a multi-pass scoring to better handle seams on regular shapes CURA-12218 The previous rework of the score calculation removed the relative comparison between vertices scores to ensure the consistency. The new strategy performed very badly on shapes where multiple points would get a similar score, e.g. finding sharp corners on a cylinder. We now process a multi-pass scoring, so that all points having a very similar score at their main criterion will now go through a second pass with different criteria. --- CMakeLists.txt | 6 + include/PathOrderOptimizer.h | 270 ++++-------------- include/utils/CriterionScore.h | 31 -- include/utils/Score.h | 61 ---- include/utils/scoring/BestElementFinder.h | 81 ++++++ .../utils/scoring/CornerScoringCriterion.h | 67 +++++ .../utils/scoring/DistanceScoringCriterion.h | 32 +++ .../scoring/ExclusionAreaScoringCriterion.h | 34 +++ .../utils/scoring/RandomScoringCriterion.h | 27 ++ include/utils/scoring/ScoringCriterion.h | 34 +++ src/utils/scoring/BestElementFinder.cpp | 74 +++++ src/utils/scoring/CornerScoringCriterion.cpp | 120 ++++++++ .../scoring/DistanceScoringCriterion.cpp | 33 +++ .../scoring/ExclusionAreaScoringCriterion.cpp | 24 ++ src/utils/scoring/RandomScoringCriterion.cpp | 20 ++ 15 files changed, 604 insertions(+), 310 deletions(-) delete mode 100644 include/utils/CriterionScore.h delete mode 100644 include/utils/Score.h create mode 100644 include/utils/scoring/BestElementFinder.h create mode 100644 include/utils/scoring/CornerScoringCriterion.h create mode 100644 include/utils/scoring/DistanceScoringCriterion.h create mode 100644 include/utils/scoring/ExclusionAreaScoringCriterion.h create mode 100644 include/utils/scoring/RandomScoringCriterion.h create mode 100644 include/utils/scoring/ScoringCriterion.h create mode 100644 src/utils/scoring/BestElementFinder.cpp create mode 100644 src/utils/scoring/CornerScoringCriterion.cpp create mode 100644 src/utils/scoring/DistanceScoringCriterion.cpp create mode 100644 src/utils/scoring/ExclusionAreaScoringCriterion.cpp create mode 100644 src/utils/scoring/RandomScoringCriterion.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index daf6022fbb..36b1ffa618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,12 @@ set(engine_SRCS # Except main.cpp. src/utils/VoxelUtils.cpp src/utils/MixedPolylineStitcher.cpp + src/utils/scoring/BestElementFinder.cpp + src/utils/scoring/CornerScoringCriterion.cpp + src/utils/scoring/DistanceScoringCriterion.cpp + src/utils/scoring/ExclusionAreaScoringCriterion.cpp + src/utils/scoring/RandomScoringCriterion.cpp + src/geometry/Point2LL.cpp src/geometry/Point3LL.cpp src/geometry/Polygon.cpp diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index d7ad78eea8..252890a635 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -21,10 +21,14 @@ #include "path_ordering.h" #include "settings/EnumSettings.h" //To get the seam settings. #include "settings/ZSeamConfig.h" //To read the seam configuration. -#include "utils/Score.h" #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. #include "utils/math.h" #include "utils/polygonUtils.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" +#include "utils/scoring/ExclusionAreaScoringCriterion.h" +#include "utils/scoring/RandomScoringCriterion.h" #include "utils/views/dfs.h" namespace cura @@ -723,166 +727,74 @@ class PathOrderOptimizer } // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. + const PointsSet& points = *path.converted_; - // ########## Step 1: define the weighs of each criterion + // ########## Step 1: define the main criteria to be applied and their weights // Standard weight for the "main" selection criterion, depending on the selected strategy. There should be // exactly one calculation using this criterion. - constexpr double weight_main_criterion = 1.0; + BestElementFinder best_candidate_finder; + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; - // If seam is not "shortest", we still compute the shortest distance score but with a very low weight - const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? weight_main_criterion : 0.02; + BestElementFinder::WeighedCriterion main_criterion; - // Avoiding overhangs is more important than the rest - constexpr double weight_exclude_overhang = 2.0; - - // In order to avoid jumping seams, we give a small score to the vertex X and Y position, so that if we have - // e.g. multiple corners with the same angle, we will always choose the ones at the top-right - constexpr double weight_consistency_x = 0.05; - constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects - - // ########## Step 2: define which criteria should be taken into account in the total score - const bool calculate_forced_pos_score = path.force_start_index_.has_value(); - - bool calculate_distance_score = false; - bool calculate_corner_score = false; - bool calculate_random_score = false; - if (! calculate_forced_pos_score) + if (path.force_start_index_.has_value()) // Actually handles EZSeamType::USER_SPECIFIED { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - calculate_distance_score = path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; - - calculate_corner_score - = path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER - && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN); - - calculate_random_score = path.seam_config_.type_ == EZSeamType::RANDOM; + main_criterion.criterion = std::make_shared(points, points.at(path.force_start_index_.value())); } - - const bool calculate_consistency_score = calculate_distance_score || calculate_corner_score; - - // Whatever the strategy, always avoid overhang - const bool calculate_overhang_score = ! overhang_areas_.empty(); - - // ########## Step 3: calculate some values that we are going to need multiple times, depending on which scoring is active - const AABB path_bounding_box = calculate_consistency_score ? AABB(*path.converted_) : AABB(); - - const Point2LL forced_start_pos = path.force_start_index_.has_value() ? path.converted_->at(path.force_start_index_.value()) : Point2LL(); - - std::vector segments_sizes; - coord_t total_length = 0; - if (calculate_corner_score) + else { - segments_sizes.resize(path.converted_->size()); - for (size_t i = 0; i < path.converted_->size(); ++i) + if (path.seam_config_.type_ == EZSeamType::SHORTEST) { - const Point2LL& here = path.converted_->at(i); - const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); - const coord_t segment_size = vSize(next - here); - segments_sizes[i] = segment_size; - total_length += segment_size; + main_criterion.criterion = std::make_shared(points, target_pos); } - } - - auto scoreFromDistance = [](const Point2LL& here, const Point2LL& remote_pos) -> double - { - // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered - // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 - constexpr double distance_divider = 20.0; - - // Use actual (non-squared) distance to ensure a proper scoring distribution - const double distance = vSizeMM(here - remote_pos); - - // Use reciprocal function to normalize distance score decreasingly - return 1.0 / (1.0 + (distance / distance_divider)); - }; - - // ########## Step 4: now calculate the total score of each vertex and select the best one - std::optional best_i; - Score best_score; - for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) - { - Score vertex_score; - - if (calculate_forced_pos_score) + else if ( + path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)) { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_main_criterion }; + main_criterion.criterion = std::make_shared(points, path.seam_config_.corner_pref_); } - - if (calculate_distance_score) + else if (path.seam_config_.type_ == EZSeamType::RANDOM) { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; + main_criterion.criterion = std::make_shared(); } + } - if (calculate_corner_score) - { - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) - - CriterionScore score_corner{ .weight = weight_main_criterion }; - - switch (path.seam_config_.corner_pref_) - { - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - // Still give sharper corners more advantage. - score_corner.score = std::abs(corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: - // Give sharper corners some advantage, but sharper concave corners even more. - if (corner_angle < 0) - { - score_corner.score = -corner_angle; - } - else - { - score_corner.score = corner_angle / 2.0; - } - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - case EZSeamCornerPrefType::PLUGIN: - break; - } + if (main_criterion.criterion) + { + main_criteria_pass.criteria.push_back(main_criterion); + } + else + { + spdlog::warn("Missing main criterion calculator"); + } - vertex_score += score_corner; - } + // Second criterion with heigher weight to avoid overhanging areas + if (! overhang_areas_.empty()) + { + BestElementFinder::WeighedCriterion overhang_criterion; + overhang_criterion.weight = 2.0; + overhang_criterion.criterion = std::make_shared(points, overhang_areas_); + main_criteria_pass.criteria.push_back(overhang_criterion); + } - if (calculate_random_score) - { - vertex_score += CriterionScore{ .score = cura::randf(), .weight = weight_main_criterion }; - } + best_candidate_finder.appendCriteriaPass(main_criteria_pass); - if (calculate_consistency_score) - { - CriterionScore score_consistency_x{ .weight = weight_consistency_x }; - score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); - vertex_score += score_consistency_x; + // ########## Step 2: add a second pass for criteria with very similar scores (e.g. corner on a cylinder) + BestElementFinder::CriteriaPass fallback_criteria_pass; - CriterionScore score_consistency_y{ .weight = weight_consistency_y }; - score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); - vertex_score += score_consistency_y; - } + BestElementFinder::WeighedCriterion fallback_criterion; + // fallback strategy is to take points on the back-most position, relative to the path + const AABB path_bounding_box(points); + Point2LL fallback_position = path_bounding_box.max_; + fallback_position.X -= (path_bounding_box.max_.X - path_bounding_box.min_.X) / 2.0; + fallback_criterion.criterion = std::make_shared(points, fallback_position); - if (calculate_overhang_score) - { - CriterionScore score_exclude_overhang{ .weight = weight_exclude_overhang }; - score_exclude_overhang.score = overhang_areas_.inside(here, true) ? 0.0 : 1.0; - vertex_score += score_exclude_overhang; - } + fallback_criteria_pass.criteria.push_back(fallback_criterion); + best_candidate_finder.appendCriteriaPass(fallback_criteria_pass); - if (! best_i.has_value() || vertex_score > best_score) - { - best_i = i; - best_score = vertex_score; - } - } + // ########## Step 3: apply the criteria to find the vertex with the best global score + std::optional best_i = best_candidate_finder.findBestElement(points.size()); if (! disallowed_area_for_seams.empty()) { @@ -891,84 +803,6 @@ class PathOrderOptimizer return best_i.value_or(0); } - /*! - * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point - * is computed by travelling on the path and stopping when the distance has been reached, For example: - * |------|---------|------|--------------*---| - * H A B C N D - * In this case, H is the start point of the path and ABCD are the actual following points of the path. - * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. - * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path - * at a given physical distance. - * \param path The vertex data of a path - * \param here The starting point index - * \param distance The distance we want to travel on the path, which may be positive to go forward - * or negative to go backward - * \param segments_sizes The pre-computed sizes of the segments - * \return The position of the path a the given distance from the reference point - */ - static Point2LL findNeighbourPoint(const OrderablePath& path, int here, coord_t distance, const std::vector& segments_sizes) - { - const int direction = distance > 0 ? 1 : -1; - const int size_delta = distance > 0 ? -1 : 0; - distance = std::abs(distance); - - // Travel on the path until we reach the distance - int actual_delta = 0; - coord_t travelled_distance = 0; - coord_t segment_size = 0; - while (travelled_distance < distance) - { - actual_delta += direction; - segment_size = segments_sizes[(here + actual_delta + size_delta + path.converted_->size()) % path.converted_->size()]; - travelled_distance += segment_size; - } - - const Point2LL& next_pos = path.converted_->at((here + actual_delta + path.converted_->size()) % path.converted_->size()); - - if (travelled_distance > distance) [[likely]] - { - // We have overtaken the required distance, go backward on the last segment - int prev = (here + actual_delta - direction + path.converted_->size()) % path.converted_->size(); - const Point2LL& prev_pos = path.converted_->at(prev); - - const Point2LL vector = next_pos - prev_pos; - const Point2LL unit_vector = (vector * 1000) / segment_size; - const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); - return prev_pos + vector_delta / 1000; - } - else - { - // Luckily, the required distance stops exactly on an existing point - return next_pos; - } - } - - /*! - * Some models have very sharp corners, but also have a high resolution. If a sharp corner - * consists of many points each point individual might have a shallow corner, but the - * collective angle of all nearby points is greater. To counter this the cornerAngle is - * calculated from two points within angle_query_distance of the query point, no matter - * what segment this leads us to - * \param path The vertex data of a path - * \param i index of the query point - * \param segments_sizes The pre-computed sizes of the segments - * \param total_length The path total length - * \param angle_query_distance query range (default to 1mm) - * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] - */ - static double cornerAngle(const OrderablePath& path, int i, const std::vector& segments_sizes, coord_t total_length, const coord_t angle_query_distance = 1000) - { - const coord_t bounded_distance = std::min(angle_query_distance, total_length / 2); - const Point2LL& here = path.converted_->at(i); - const Point2LL next = findNeighbourPoint(path, i, bounded_distance, segments_sizes); - const Point2LL previous = findNeighbourPoint(path, i, -bounded_distance, segments_sizes); - - double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; - - return angle / std::numbers::pi; - } - /*! * Calculate the direct Euclidean distance to move from one point to * another. diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h deleted file mode 100644 index e4313d988e..0000000000 --- a/include/utils/CriterionScore.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2024 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef UTILS_CRITERION_SCORE_H -#define UTILS_CRITERION_SCORE_H - -namespace cura -{ - -/*! - * This structure represents a score given by a single crtierion when calculating a global score to select a best - * candidate among a list with multiple criteria. - */ -struct CriterionScore -{ - /*! - * The score given by the criterion. To ensure a proper selection, this value must be contained in [0.0, 1.0] and - * the different given scores must be evenly distributed in this range. - */ - double score{ 0.0 }; - - /*! - * The weight to be given when taking this score into the global score. A score that contributes "normally" to the - * global score should have a weight of 1.0, and others should be adjusted around this value, to give them more or - * less influence. - */ - double weight{ 0.0 }; -}; - -} // namespace cura -#endif // UTILS_CRITERION_SCORE_H diff --git a/include/utils/Score.h b/include/utils/Score.h deleted file mode 100644 index f8108498ee..0000000000 --- a/include/utils/Score.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2024 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef UTILS_SCORE_H -#define UTILS_SCORE_H - -#include - -#include "CriterionScore.h" - -namespace cura -{ - -/*! - * This class represents a score to be calculated over different criteria, to select the best candidate among a list. - */ -class Score -{ -private: - double value_{ 0.0 }; - -public: - /*! - * Get the actual score value, should be used for debug purposes only - */ - double getValue() const - { - return value_; - } - - /*! - * Add the calculated score of an inidividual criterion to the global score, taking care of its weight - */ - void operator+=(const CriterionScore& criterion_score) - { - value_ += criterion_score.score * criterion_score.weight; - } - - /*! - * Comparison operators to allow selecting the best global score - */ - auto operator<=>(const Score&) const = default; -}; - -} // namespace cura - -namespace fmt -{ - -template<> -struct formatter : formatter -{ - auto format(const cura::Score& score, format_context& ctx) - { - return fmt::format_to(ctx.out(), "Score{{{}}}", score.getValue()); - } -}; - -} // namespace fmt - -#endif // UTILS_SCORE_H diff --git a/include/utils/scoring/BestElementFinder.h b/include/utils/scoring/BestElementFinder.h new file mode 100644 index 0000000000..25cc4fae07 --- /dev/null +++ b/include/utils/scoring/BestElementFinder.h @@ -0,0 +1,81 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_BESTCANDIDATEFINDER_H +#define UTILS_SCORING_BESTCANDIDATEFINDER_H + +#include +#include +#include +#include + +namespace cura +{ +class ScoringCriterion; + +/*! + * This class implements an algorithm to find an element amongst a list, regarding one or multiple scoring criteria. The + * criteria are implemented by subclassing the CriterionScoring class. It is also possible to setup multiple passes of + * criteria. Thus, if the first pass gives a few best results that are too close to each other, a new pass is processed + * with different criteria, and so on until we have a single outsider or no more criteria. + */ +class BestElementFinder +{ +private: + /*! + * Contains the index of an element in the source list, and its calculated score + */ + struct Candidate + { + size_t candidate_index; + double score{ 0.0 }; + }; + +public: + /*! + * Contains a criterion to be processed to calculate the score of an element, and the weight is has on the global + * score calculation. + */ + struct WeighedCriterion + { + std::shared_ptr criterion; + + /*! + * The weight to be given when taking this criterion into the global score. A score that contributes "normally" + * to the global score should have a weight of 1.0, and others should be adjusted around this value, to give + * them more or less influence. + */ + double weight{ 1.0 }; + }; + + /*! + * Contains multiple criteria to be processed on each element in a single pass + */ + struct CriteriaPass + { + std::vector criteria; + + /*! + * Once we have calculated the global scores of each element for this pass, we calculate the score difference + * between the best candidate and the following ones. If the following ones have a score close enough to the + * best, within this threshold, they will also be considered outsiders and will be run for the next pass. + */ + double outsider_delta_threshold{ 0.0 }; + }; + +private: + std::vector criteria_; + +public: + explicit BestElementFinder() = default; + + void appendCriteriaPass(const CriteriaPass& pass) + { + criteria_.push_back(pass); + } + + std::optional findBestElement(const size_t candidates_count); +}; + +} // namespace cura +#endif // UTILS_SCORING_BESTCANDIDATEFINDER_H diff --git a/include/utils/scoring/CornerScoringCriterion.h b/include/utils/scoring/CornerScoringCriterion.h new file mode 100644 index 0000000000..210416f56b --- /dev/null +++ b/include/utils/scoring/CornerScoringCriterion.h @@ -0,0 +1,67 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef CORNERSCORINGCRITERION_H +#define CORNERSCORINGCRITERION_H + +#include + +#include "geometry/Point2LL.h" +#include "settings/EnumSettings.h" +#include "utils/Coord_t.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to whether the point is creating a corner or lies on a flat line. + * Depending on the given preference, concave or convex corners may get a higher score. + */ +class CornerScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const EZSeamCornerPrefType corner_preference_; + std::vector segments_sizes_; + coord_t total_length_{ 0 }; + +public: + explicit CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference); + + virtual double computeScore(const size_t candidate_index) const override; + +private: + /*! + * Some models have very sharp corners, but also have a high resolution. If a sharp corner + * consists of many points each point individual might have a shallow corner, but the + * collective angle of all nearby points is greater. To counter this the cornerAngle is + * calculated from two points within angle_query_distance of the query point, no matter + * what segment this leads us to + * \param vertex_index index of the query point + * \param angle_query_distance query range (default to 1mm) + * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] + */ + double cornerAngle(size_t vertex_index, const coord_t angle_query_distance = 1000) const; + + /*! + * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point + * is computed by travelling on the path and stopping when the distance has been reached, For example: + * |------|---------|------|--------------*---| + * H A B C N D + * In this case, H is the start point of the path and ABCD are the actual following points of the path. + * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. + * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path + * at a given physical distance. + * \param vertex_index The starting point index + * \param distance The distance we want to travel on the path, which may be positive to go forward + * or negative to go backward + * \return The position of the path a the given distance from the reference point + */ + Point2LL findNeighbourPoint(size_t vertex_index, coord_t distance) const; +}; + +} // namespace cura + +#endif // CORNERSCORINGCRITERION_H diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h new file mode 100644 index 0000000000..8c520fb1ed --- /dev/null +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -0,0 +1,32 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef DISTANCESCORINGCRITERION_H +#define DISTANCESCORINGCRITERION_H + +#include "geometry/Point2LL.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to the distance from the point to a target point. Closer points will get + * a higher score. + */ +class DistanceScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const Point2LL& target_pos_; + +public: + explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // DISTANCESCORINGCRITERION_H diff --git a/include/utils/scoring/ExclusionAreaScoringCriterion.h b/include/utils/scoring/ExclusionAreaScoringCriterion.h new file mode 100644 index 0000000000..84fd0ab7a0 --- /dev/null +++ b/include/utils/scoring/ExclusionAreaScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef EXCLUSIONAREASCORINGCRITERION_H +#define EXCLUSIONAREASCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; +class Shape; + +/*! + * Criterion that will give a score according to whether the point is located inside or outside of an exclusion area. + * This is currently a binary test and the score will be either 0 or 1. + */ +class ExclusionAreaScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const Shape& exclusion_area_; + +public: + explicit ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // EXCLUSIONAREASCORINGCRITERION_H diff --git a/include/utils/scoring/RandomScoringCriterion.h b/include/utils/scoring/RandomScoringCriterion.h new file mode 100644 index 0000000000..5a577be894 --- /dev/null +++ b/include/utils/scoring/RandomScoringCriterion.h @@ -0,0 +1,27 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef RANDOMSCORINGCRITERION_H +#define RANDOMSCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +/*! + * Criterion that will give a random score whatever the element is. + */ +class RandomScoringCriterion : public ScoringCriterion +{ +public: + explicit RandomScoringCriterion(); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // RANDOMSCORINGCRITERION_H diff --git a/include/utils/scoring/ScoringCriterion.h b/include/utils/scoring/ScoringCriterion.h new file mode 100644 index 0000000000..41280b0a82 --- /dev/null +++ b/include/utils/scoring/ScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_SCORINGCRITERION_H +#define UTILS_SCORING_SCORINGCRITERION_H + +#include + +namespace cura +{ + +/*! + * Base class for implementing a selection criterion when calculating a multi-criteria score to select the best element + * amongst a list. + */ +class ScoringCriterion +{ +public: + ScoringCriterion() = default; + + virtual ~ScoringCriterion() = default; + + /*! + * \brief Computes the score of an element regarding this criterion. To ensure a proper selection, this value must + * be contained in [0.0, 1.0] and the different given scores must be evenly distributed in this range. + * \param candidate_index The index of the candidate of the original list + * \return The raw score of the element regarding this criterion + */ + virtual double computeScore(const size_t candidate_index) const = 0; +}; + +} // namespace cura + +#endif // UTILS_SCORING_SCORINGCRITERION_H diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp new file mode 100644 index 0000000000..20330a108b --- /dev/null +++ b/src/utils/scoring/BestElementFinder.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/BestElementFinder.h" + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) +{ + if (candidates_count == 0) + { + return std::nullopt; + } + + // Start by initializing the candidates list in natural order + std::vector candidates(candidates_count); + for (size_t i = 0; i < candidates_count; ++i) + { + candidates[i].candidate_index = i; + } + + const auto begin = candidates.begin(); + auto end = candidates.end(); + + // Now run the criteria passes until we have a single outsider or no more cirteria + for (const CriteriaPass& criteria_pass : criteria_) + { + // For each element, reset score, process each criterion and apply wweights to get the global score + for (auto iterator = begin; iterator != end; ++iterator) + { + iterator->score = 0.0; + + for (const auto& weighed_criterion : criteria_pass.criteria) + { + iterator->score += weighed_criterion.criterion->computeScore(iterator->candidate_index) * weighed_criterion.weight; + } + } + + // Now sort the candiates (sub)list to get the best candidates first + std::sort( + begin, + end, + [](const Candidate& candidate1, const Candidate& candidate2) + { + return candidate1.score > candidate2.score; + }); + + // Check whether the following candidates have a score similar enough to the actual best one, and skip others + if (criteria_pass.outsider_delta_threshold > 0.0) + { + const double highest_score = candidates.front().score; + auto iterator = std::next(begin); + while (iterator != end && (highest_score - iterator->score) <= criteria_pass.outsider_delta_threshold) + { + ++iterator; + } + + end = iterator; + } + + if (std::distance(begin, end) <= 1) + { + // We have a single outsider, don't go further + break; + } + } + + return begin->candidate_index; +} + +} // namespace cura diff --git a/src/utils/scoring/CornerScoringCriterion.cpp b/src/utils/scoring/CornerScoringCriterion.cpp new file mode 100644 index 0000000000..05451e6e6d --- /dev/null +++ b/src/utils/scoring/CornerScoringCriterion.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/CornerScoringCriterion.h" + +#include "geometry/PointsSet.h" +#include "utils/linearAlg2D.h" +#include "utils/math.h" + + +namespace cura +{ + +CornerScoringCriterion::CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference) + : points_(points) + , corner_preference_(corner_preference) + , segments_sizes_(points.size()) +{ + // Pre-calculate the segments lengths because we are going to need them multiple times + for (size_t i = 0; i < points.size(); ++i) + { + const Point2LL& here = points_.at(i); + const Point2LL& next = points_.at((i + 1) % points_.size()); + const coord_t segment_size = vSize(next - here); + segments_sizes_[i] = segment_size; + total_length_ += segment_size; + } +} + +double CornerScoringCriterion::computeScore(const size_t candidate_index) const +{ + double corner_angle = cornerAngle(candidate_index); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) + + double score = 0.0; + + switch (corner_preference_) + { + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score = -corner_angle; + } + else + { + score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; + } + + return score; +} + +double CornerScoringCriterion::cornerAngle(size_t vertex_index, const coord_t angle_query_distance) const +{ + const coord_t bounded_distance = std::min(angle_query_distance, total_length_ / 2); + const Point2LL& here = points_.at(vertex_index); + const Point2LL next = findNeighbourPoint(vertex_index, bounded_distance); + const Point2LL previous = findNeighbourPoint(vertex_index, -bounded_distance); + + double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; + + return angle / std::numbers::pi; +} + +Point2LL CornerScoringCriterion::findNeighbourPoint(size_t vertex_index, coord_t distance) const +{ + const int direction = distance > 0 ? 1 : -1; + const int size_delta = distance > 0 ? -1 : 0; + distance = std::abs(distance); + + // Travel on the path until we reach the distance + int actual_delta = 0; + coord_t travelled_distance = 0; + coord_t segment_size = 0; + while (travelled_distance < distance) + { + actual_delta += direction; + segment_size = segments_sizes_[(vertex_index + actual_delta + size_delta + points_.size()) % points_.size()]; + travelled_distance += segment_size; + } + + const Point2LL& next_pos = points_.at((vertex_index + actual_delta + points_.size()) % points_.size()); + + if (travelled_distance > distance) [[likely]] + { + // We have overtaken the required distance, go backward on the last segment + int prev = (vertex_index + actual_delta - direction + points_.size()) % points_.size(); + const Point2LL& prev_pos = points_.at(prev); + + const Point2LL vector = next_pos - prev_pos; + const Point2LL unit_vector = (vector * 1000) / segment_size; + const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); + return prev_pos + vector_delta / 1000; + } + else + { + // Luckily, the required distance stops exactly on an existing point + return next_pos; + } +} + +} // namespace cura diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp new file mode 100644 index 0000000000..8b901a71fc --- /dev/null +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/DistanceScoringCriterion.h" + +#include "geometry/PointsSet.h" + + +namespace cura +{ + +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos) + : points_(points) + , target_pos_(target_pos) +{ +} + +double DistanceScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + + // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + constexpr double distance_divider = 20.0; + + // Use actual (non-squared) distance to ensure a proper scoring distribution + const double distance = vSizeMM(candidate_position - target_pos_); + + // Use reciprocal function to normalize distance score decreasingly + return 1.0 / (1.0 + (distance / distance_divider)); +} + +} // namespace cura diff --git a/src/utils/scoring/ExclusionAreaScoringCriterion.cpp b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp new file mode 100644 index 0000000000..c4bdd097ed --- /dev/null +++ b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/ExclusionAreaScoringCriterion.h" + +#include "geometry/Shape.h" + + +namespace cura +{ + +ExclusionAreaScoringCriterion::ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area) + : points_(points) + , exclusion_area_(exclusion_area) +{ +} + +double ExclusionAreaScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + return exclusion_area_.inside(candidate_position, true) ? 0.0 : 1.0; +} + +} // namespace cura diff --git a/src/utils/scoring/RandomScoringCriterion.cpp b/src/utils/scoring/RandomScoringCriterion.cpp new file mode 100644 index 0000000000..de6c81280c --- /dev/null +++ b/src/utils/scoring/RandomScoringCriterion.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/RandomScoringCriterion.h" + +#include "utils/math.h" + +namespace cura +{ + +RandomScoringCriterion::RandomScoringCriterion() +{ +} + +double RandomScoringCriterion::computeScore(const size_t /*candidate_index*/) const +{ + return cura::randf(); +} + +} // namespace cura From 2dcbd3037747b522cddfbd5a7f0f7ca55d2c66aa Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 14:02:10 +0100 Subject: [PATCH 098/139] Add benchmark to allow testing different scoring strategies CURA-12218 --- benchmark/scoring_benchmark.h | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 benchmark/scoring_benchmark.h diff --git a/benchmark/scoring_benchmark.h b/benchmark/scoring_benchmark.h new file mode 100644 index 0000000000..b526c94302 --- /dev/null +++ b/benchmark/scoring_benchmark.h @@ -0,0 +1,73 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_SCORING_BENCHMARK_H +#define CURAENGINE_SCORING_BENCHMARK_H + +#include + +#include "geometry/PointsSet.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" + +namespace cura +{ +class ScoringTestFixture : public benchmark::Fixture +{ +public: + PointsSet points; + + void SetUp(const ::benchmark::State& state) override + { + points.resize(state.range(0)); + constexpr double radius = 5000.0; + + for (size_t i = 0; i < points.size(); ++i) + { + const double angle = (i * std::numbers::pi * 2.0) / points.size(); + points[i] = Point2LL(std::cos(angle) * radius, std::sin(angle) * radius); + } + } + + void TearDown(const ::benchmark::State& state) override + { + } +}; + +BENCHMARK_DEFINE_F(ScoringTestFixture, ScoringTest_WorstCase)(benchmark::State& st) +{ + for (auto _ : st) + { + BestElementFinder best_element_finder; + + // Pass 1 : find corners + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; + + BestElementFinder::WeighedCriterion main_criterion; + main_criterion.criterion = std::make_shared(points, EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED); + main_criteria_pass.criteria.push_back(main_criterion); + + best_element_finder.appendCriteriaPass(main_criteria_pass); + + // Pass 2 : fallback to distance calculation + BestElementFinder::CriteriaPass fallback_criteria_pass; + BestElementFinder::WeighedCriterion fallback_criterion; + fallback_criterion.criterion = std::make_shared(points, Point2LL(1000, 0)); + + fallback_criteria_pass.criteria.push_back(fallback_criterion); + best_element_finder.appendCriteriaPass(fallback_criteria_pass); + + best_element_finder.findBestElement(points.size()); + } +} + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(1000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10)->Unit(benchmark::kMillisecond); + +} // namespace cura +#endif // CURAENGINE_SCORING_BENCHMARK_H From 6ccc9a04dd5f79a5a90f4464705eaa91f14da398 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 15:28:22 +0100 Subject: [PATCH 099/139] Optimize best element finding CURA-12218 Instead of sorting the whole scores list, which is very costly, pre- calculate the best score and only keep elements that are close enough to it. --- src/utils/scoring/BestElementFinder.cpp | 48 ++++++++++--------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp index 20330a108b..c0e2f8da6e 100644 --- a/src/utils/scoring/BestElementFinder.cpp +++ b/src/utils/scoring/BestElementFinder.cpp @@ -3,6 +3,8 @@ #include "utils/scoring/BestElementFinder.h" +#include + #include "utils/scoring/ScoringCriterion.h" namespace cura @@ -10,25 +12,21 @@ namespace cura std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) { - if (candidates_count == 0) - { - return std::nullopt; - } - // Start by initializing the candidates list in natural order - std::vector candidates(candidates_count); + std::vector best_candidates(candidates_count); for (size_t i = 0; i < candidates_count; ++i) { - candidates[i].candidate_index = i; + best_candidates[i].candidate_index = i; } - const auto begin = candidates.begin(); - auto end = candidates.end(); + const auto begin = best_candidates.begin(); + auto end = best_candidates.end(); // Now run the criteria passes until we have a single outsider or no more cirteria for (const CriteriaPass& criteria_pass : criteria_) { - // For each element, reset score, process each criterion and apply wweights to get the global score + // For each element, reset score, process each criterion and apply weights to get the global score + double best_score = 0.0; for (auto iterator = begin; iterator != end; ++iterator) { iterator->score = 0.0; @@ -37,38 +35,28 @@ std::optional cura::BestElementFinder::findBestElement(const size_t cand { iterator->score += weighed_criterion.criterion->computeScore(iterator->candidate_index) * weighed_criterion.weight; } + + best_score = std::max(best_score, iterator->score); } - // Now sort the candiates (sub)list to get the best candidates first - std::sort( + // Skip candidates that have a score too far from the actual best one + const double delta_threshold = criteria_pass.outsider_delta_threshold + std::numeric_limits::epsilon(); + end = std::remove_if( begin, end, - [](const Candidate& candidate1, const Candidate& candidate2) + [&best_score, &delta_threshold](const Candidate& candidate) { - return candidate1.score > candidate2.score; + return best_score - candidate.score > delta_threshold; }); - // Check whether the following candidates have a score similar enough to the actual best one, and skip others - if (criteria_pass.outsider_delta_threshold > 0.0) - { - const double highest_score = candidates.front().score; - auto iterator = std::next(begin); - while (iterator != end && (highest_score - iterator->score) <= criteria_pass.outsider_delta_threshold) - { - ++iterator; - } - - end = iterator; - } - - if (std::distance(begin, end) <= 1) + if (std::distance(begin, end) == 1) { // We have a single outsider, don't go further - break; + return begin->candidate_index; } } - return begin->candidate_index; + return begin != end ? std::make_optional(begin->candidate_index) : std::nullopt; } } // namespace cura From 12491b406a5687fb961b556a8fdf61f1a14a261b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 15:48:41 +0100 Subject: [PATCH 100/139] Fix Mac build CURA-12176 --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index a8642cf95c..65eae9c2c2 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1730,7 +1730,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& constexpr Ratio flow_ratio = 1.0; constexpr bool always_retract = false; constexpr bool reverse_order = false; - constexpr std::optional start_near_location = std::nullopt; + const std::optional start_near_location = std::nullopt; constexpr bool scarf_seam = true; constexpr bool smooth_speed = true; From 71ac23e50ea58b7ec08a5823b0bbd5a03dec1c25 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 09:58:58 +0100 Subject: [PATCH 101/139] Fix some cases of unaligned seams (e.g. cubes) CURA-12218 In order to restore the previous seam consistency even better, we now use two different fallback passes, the first one working on Y only, and the second one on X only. This way it really mimics the former behavior and always aligns the seam even on very symmetric shapes. --- include/PathOrderOptimizer.h | 21 ++++++++++--------- include/utils/scoring/BestElementFinder.h | 10 +++++++-- .../utils/scoring/DistanceScoringCriterion.h | 11 +++++++++- src/utils/scoring/BestElementFinder.cpp | 11 ++++++++++ .../scoring/DistanceScoringCriterion.cpp | 19 ++++++++++++++--- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 252890a635..ae01be4b25 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -780,18 +780,19 @@ class PathOrderOptimizer best_candidate_finder.appendCriteriaPass(main_criteria_pass); - // ########## Step 2: add a second pass for criteria with very similar scores (e.g. corner on a cylinder) - BestElementFinder::CriteriaPass fallback_criteria_pass; - - BestElementFinder::WeighedCriterion fallback_criterion; - // fallback strategy is to take points on the back-most position, relative to the path + // ########## Step 2: add fallback passes for criteria with very similar scores (e.g. corner on a cylinder) const AABB path_bounding_box(points); - Point2LL fallback_position = path_bounding_box.max_; - fallback_position.X -= (path_bounding_box.max_.X - path_bounding_box.min_.X) / 2.0; - fallback_criterion.criterion = std::make_shared(points, fallback_position); - fallback_criteria_pass.criteria.push_back(fallback_criterion); - best_candidate_finder.appendCriteriaPass(fallback_criteria_pass); + { // First fallback strategy is to take points on the back-most position + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::YOnly); + constexpr double outsider_delta_threshold = 0.01; + best_candidate_finder.appendSingleCriterionPass(fallback_criterion, outsider_delta_threshold); + } + + { // Second fallback strategy, in case we still have multiple points that are aligned on Y (e.g. cube), take the right-most point + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::XOnly); + best_candidate_finder.appendSingleCriterionPass(fallback_criterion); + } // ########## Step 3: apply the criteria to find the vertex with the best global score std::optional best_i = best_candidate_finder.findBestElement(points.size()); diff --git a/include/utils/scoring/BestElementFinder.h b/include/utils/scoring/BestElementFinder.h index 25cc4fae07..e2ede975d5 100644 --- a/include/utils/scoring/BestElementFinder.h +++ b/include/utils/scoring/BestElementFinder.h @@ -16,8 +16,9 @@ class ScoringCriterion; /*! * This class implements an algorithm to find an element amongst a list, regarding one or multiple scoring criteria. The * criteria are implemented by subclassing the CriterionScoring class. It is also possible to setup multiple passes of - * criteria. Thus, if the first pass gives a few best results that are too close to each other, a new pass is processed - * with different criteria, and so on until we have a single outsider or no more criteria. + * criteria. Thus, if the first pass gives a few best candidates that are too close to each other, we keep only the best + * candidates and process a second pass with different criteria, and so on until we have a single outsider, or no more + * criteria. */ class BestElementFinder { @@ -74,6 +75,11 @@ class BestElementFinder criteria_.push_back(pass); } + /*! + * Convenience method to add a pass with a single criterion + */ + void appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold = 0.0); + std::optional findBestElement(const size_t candidates_count); }; diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h index 8c520fb1ed..3b6d8785d3 100644 --- a/include/utils/scoring/DistanceScoringCriterion.h +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -17,12 +17,21 @@ class PointsSet; */ class DistanceScoringCriterion : public ScoringCriterion { +public: + enum class DistanceType + { + Euclidian, // Classic euclidian distance between the points + XOnly, // Only difference on X coordinate + YOnly, // Only difference on Y coordinate + }; + private: const PointsSet& points_; const Point2LL& target_pos_; + const DistanceType distance_type_; public: - explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos); + explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type = DistanceType::Euclidian); virtual double computeScore(const size_t candidate_index) const override; }; diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp index c0e2f8da6e..ecd63e89cb 100644 --- a/src/utils/scoring/BestElementFinder.cpp +++ b/src/utils/scoring/BestElementFinder.cpp @@ -10,6 +10,17 @@ namespace cura { +void BestElementFinder::appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold) +{ + WeighedCriterion weighed_criterion; + weighed_criterion.criterion = criterion; + + CriteriaPass criteria_pass; + criteria_pass.outsider_delta_threshold = outsider_delta_threshold; + criteria_pass.criteria.push_back(weighed_criterion); + appendCriteriaPass(criteria_pass); +} + std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) { // Start by initializing the candidates list in natural order diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp index 8b901a71fc..0c68e875b8 100644 --- a/src/utils/scoring/DistanceScoringCriterion.cpp +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -9,9 +9,10 @@ namespace cura { -DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos) +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type) : points_(points) , target_pos_(target_pos) + , distance_type_(distance_type) { } @@ -23,8 +24,20 @@ double DistanceScoringCriterion::computeScore(const size_t candidate_index) cons // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 constexpr double distance_divider = 20.0; - // Use actual (non-squared) distance to ensure a proper scoring distribution - const double distance = vSizeMM(candidate_position - target_pos_); + double distance = 0.0; + switch (distance_type_) + { + case DistanceType::Euclidian: + // Use actual (non-squared) distance to ensure a proper scoring distribution + distance = vSizeMM(candidate_position - target_pos_); + break; + case DistanceType::XOnly: + distance = INT2MM(std::abs(candidate_position.X - target_pos_.X)); + break; + case DistanceType::YOnly: + distance = INT2MM(std::abs(candidate_position.Y - target_pos_.Y)); + break; + } // Use reciprocal function to normalize distance score decreasingly return 1.0 / (1.0 + (distance / distance_divider)); From 43d5d2da84973e2d9af69f0a94fcbcaeb307a3ce Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 11:46:41 +0100 Subject: [PATCH 102/139] Fix spiralize mode CURA-12245 This was broken when moving to 3D GCodePath points for the scarf seam --- src/LayerPlan.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..9b07d2d22a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2664,14 +2664,14 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const Point2LL p1 = spiral_path.points[point_idx].toPoint2LL(); length += vSizeMM(p0 - p1); p0 = p1; - gcode.setZ(std::round(z_ + layer_thickness_ * length / totalLength)); + const coord_t z_offset = std::round(layer_thickness_ * length / totalLength); const double extrude_speed = speed * spiral_path.speed_back_pressure_factor; writeExtrusionRelativeZ( gcode, spiral_path.points[point_idx], extrude_speed, - path.z_offset, + path.z_offset + z_offset, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, update_extrusion_offset); From 9a09cb29c051d328b3e50cba52467702de3b7cf6 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 30 Oct 2024 10:58:45 +0000 Subject: [PATCH 103/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9b07d2d22a..7d7dc3a2d1 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From ad8ee70e7b233683fed1297fc8c3accb4a2ac098 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 14:38:13 +0100 Subject: [PATCH 104/139] Fix crashes when using extra infill support lines CURA-12232 Some references were created to temporary objects, which most of the times work but sometimes crash if the memory was reallocated at the point the object is being used. --- src/FffGcodeWriter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 2417723014..b310b13c88 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2186,7 +2186,7 @@ void wall_tool_paths2lines(const std::vector>& w { for (const ExtrusionLine& c : b) { - const Polygon& poly = c.toPolygon(); + const Polygon poly = c.toPolygon(); if (c.is_closed_) { result.push_back(poly.toPseudoOpenPolyline()); @@ -2343,12 +2343,12 @@ void addExtraLinesToSupportSurfacesAbove( // invert the supported_area by adding one huge polygon around the outside supported_area.push_back(AABB{ supported_area }.toPolygon()); - const Shape& inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); + const Shape inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); OpenLinesSet unsupported_line_segments = inv_supported_area.intersection(printed_lines_on_layer_above); // This is to work around a rounding issue in the shape library with border points. - const Shape& expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); + const Shape expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. infill_line_width, @@ -2358,7 +2358,7 @@ void addExtraLinesToSupportSurfacesAbove( for (const OpenPolyline& a : unsupported_line_segments) { - const OpenPolyline& simplified = s.polyline(a); + const OpenPolyline simplified = s.polyline(a); for (const Point2LL& point : simplified) { size_t idx = expanded_inv_supported_area.findInside(point); From 917c3afc17f6c79b12327a7ce52c2665e1ef03e5 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 14:38:39 +0100 Subject: [PATCH 105/139] Slight optimization CURA-12232 --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index b310b13c88..a736d1a900 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2067,7 +2067,7 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& line_to_add) { // Returns the line index and the index of the point within an infill_line, null for no match found. - const auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](const Point2LL& p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { From 1ae45ef17a4a76c691840023857256cacf503bfa Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 17:25:59 +0100 Subject: [PATCH 106/139] Fix possible crashes when using bad meshes CURA-12232 --- src/WallToolPaths.cpp | 13 +++++++++++++ src/WallsComputation.cpp | 22 ++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index c34aaf8d69..1773f221c4 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -436,6 +436,19 @@ const Shape& WallToolPaths::getInnerContour() bool WallToolPaths::removeEmptyToolPaths(std::vector& toolpaths) { + for (VariableWidthLines& toolpath : toolpaths) + { + toolpath.erase( + std::remove_if( + toolpath.begin(), + toolpath.end(), + [](const ExtrusionLine& line) + { + return line.junctions_.empty(); + }), + toolpath.end()); + } + toolpaths.erase( std::remove_if( toolpaths.begin(), diff --git a/src/WallsComputation.cpp b/src/WallsComputation.cpp index 752d1cf0c9..f75c89defa 100644 --- a/src/WallsComputation.cpp +++ b/src/WallsComputation.cpp @@ -104,21 +104,15 @@ void WallsComputation::generateWalls(SliceLayer* layer, SectionType section) // Remove the parts which did not generate a wall. As these parts are too small to print, // and later code can now assume that there is always minimal 1 wall line. - if (settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps")) - { - for (size_t part_idx = 0; part_idx < layer->parts.size(); part_idx++) + bool check_wall_and_spiral = settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps"); + auto iterator_remove = std::remove_if( + layer->parts.begin(), + layer->parts.end(), + [&check_wall_and_spiral](const SliceLayerPart& part) { - if (layer->parts[part_idx].wall_toolpaths.empty() && layer->parts[part_idx].spiral_wall.empty()) - { - if (part_idx != layer->parts.size() - 1) - { // move existing part into part to be deleted - layer->parts[part_idx] = std::move(layer->parts.back()); - } - layer->parts.pop_back(); // always remove last element from array (is more efficient) - part_idx -= 1; // check the part we just moved here - } - } - } + return (check_wall_and_spiral && part.wall_toolpaths.empty() && part.spiral_wall.empty()) || part.outline.empty() || part.print_outline.empty(); + }); + layer->parts.erase(iterator_remove, layer->parts.end()); } void WallsComputation::generateSpiralInsets(SliceLayerPart* part, coord_t line_width_0, coord_t wall_0_inset, bool recompute_outline_based_on_outer_wall) From 69c3e42cd04d3a57e2832dda18f9a2def41f9bd5 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 31 Oct 2024 10:10:16 +0100 Subject: [PATCH 107/139] Apply code cleaning suggestions CURA-12176 --- include/LayerPlan.h | 46 +++++++++++++++++++++++++++------------------ src/LayerPlan.cpp | 30 ++++++++++------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index b2b207d3be..dd6ac02c83 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -396,6 +396,8 @@ class LayerPlan : public NoCopy * \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon * \param flow_ratio The ratio with which to multiply the extrusion amount * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygon( const Polygon& polygon, @@ -408,8 +410,7 @@ class LayerPlan : public NoCopy const Ratio& flow_ratio = 1.0_r, bool always_retract = false, bool scarf_seam = false, - bool smooth_speed = false, - bool is_candidate_small_feature = false); + bool smooth_speed = false); /*! * Add polygons to the gcode with optimized order. @@ -438,6 +439,8 @@ class LayerPlan : public NoCopy * \param reverse_order Adds polygons in reverse order. * \param start_near_location Start optimising the path near this location. * If unset, this causes it to start near the last planned location. + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygonsByOptimizer( const Shape& polygons, @@ -535,6 +538,8 @@ class LayerPlan : public NoCopy * polyline). * \param is_reversed Whether to print this wall in reverse direction. * \param is_linked_path Whether the path is a continuation off the previous path + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addWall( const ExtrusionLine& wall, @@ -844,6 +849,23 @@ class LayerPlan : public NoCopy PrintFeatureType feature, bool update_extrusion_offset = false); + /*! + * \brief Alias for a function definition that adds an extrusion segment + * \param start The start position of the segment + * \param end The end position of the segment + * \param speed_factor The speed factor to be applied when extruding this specific segment (relative to nominal speed for the entire path) + * \param flow_ratio The flow ratio to be applied when extruding this specific segment (relative to nominal flow for the entire path) + * \param line_width_ratio The line width ratio to be applied when extruding this specific segment (relative to nominal line width for the entire path) + * \param distance_to_bridge_start The calculate distance to the next bridge start, which may be irrelevant in some cases + */ + using AddExtrusionSegmentFunction = std::function; + /*! * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline @@ -902,13 +924,7 @@ class LayerPlan : public NoCopy const coord_t decelerate_length, const bool is_scarf_closure, const bool compute_distance_to_bridge_start, - const std::function& func_add_segment); + const AddExtrusionSegmentFunction& func_add_segment); /*! * \brief Add a wall to the gcode with optimized order, possibly adding a scarf seam / speed gradient according to settings @@ -922,8 +938,8 @@ class LayerPlan : public NoCopy * \param is_closed Indicates whether the path is closed (or open) * \param is_reversed Indicates if the path is to be processed backwards * \param is_candidate_small_feature Indicates whether the path should be tested for being treated as a smell feature - * \param scarf_seam Indicates whether we may set a scraf seam to the path - * \param smooth_speed Indicates whether we may set a speed gradient to the path + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters */ template @@ -939,13 +955,7 @@ class LayerPlan : public NoCopy const bool is_candidate_small_feature, const bool scarf_seam, const bool smooth_speed, - const std::function& func_add_segment); + const AddExtrusionSegmentFunction& func_add_segment); /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 66723a14bf..7646fbbbee 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -565,10 +565,10 @@ void LayerPlan::addPolygon( const Ratio& flow_ratio, bool always_retract, bool scarf_seam, - bool smooth_speed, - bool is_candidate_small_feature) + bool smooth_speed) { constexpr bool is_closed = true; + constexpr bool is_candidate_small_feature = false; Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); @@ -659,7 +659,7 @@ void LayerPlan::addPolygonsByOptimizer( } orderOptimizer.optimize(); - auto add_polygons + const auto add_polygons = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &always_retract, &scarf_seam, &smooth_speed](const auto& iterator_begin, const auto& iterator_end) { for (auto iterator = iterator_begin; iterator != iterator_end; ++iterator) @@ -1065,13 +1065,7 @@ void LayerPlan::addSplitWall( const coord_t decelerate_length, const bool is_scarf_closure, const bool compute_distance_to_bridge_start, - const std::function& func_add_segment) + const AddExtrusionSegmentFunction& func_add_segment) { coord_t distance_to_bridge_start = 0; // will be updated before each line is processed Point2LL p0 = wall.pointAt(start_idx); @@ -1098,6 +1092,9 @@ void LayerPlan::addSplitWall( if constexpr (std::is_same_v) { + // The bridging functionality has not been designed to work with anything else than ExtrusionLine objects, + // and there is no need to do it otherwise yet. So the compute_distance_to_bridge_start argument will + // just be ignored if using an other PathType (e.g. Polygon) if (compute_distance_to_bridge_start && ! bridge_wall_mask_.empty()) { distance_to_bridge_start = computeDistanceToBridgeStart(wall.getPath(), (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); @@ -1368,13 +1365,7 @@ void LayerPlan::addWallWithScarfSeam( const bool is_candidate_small_feature, const bool scarf_seam, const bool smooth_speed, - const std::function& func_add_segment) + const AddExtrusionSegmentFunction& func_add_segment) { if (wall.empty()) { @@ -1494,8 +1485,7 @@ void LayerPlan::addWall( wall.inset_idx_ == 0, scarf_seam, smooth_speed, - [this, &settings, &default_config, &roofing_config, &bridge_config, &non_bridge_line_volume]( - const Point3LL& start, + [&](const Point3LL& start, const Point3LL& end, const Ratio& speed_factor, const Ratio& actual_flow_ratio, @@ -2492,7 +2482,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 34af646bd48edde42623cb22b21d3dba11e0d454 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 31 Oct 2024 09:10:50 +0000 Subject: [PATCH 108/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7646fbbbee..0420da65e2 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2482,7 +2482,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From eeadea0dfdd1b5e7571f98e3150b7f1c4b5af9c0 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 31 Oct 2024 13:09:32 +0100 Subject: [PATCH 109/139] Fix extrusion commands with Z0 CURA-12244 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7d7dc3a2d1..9e613fadae 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2880,7 +2880,7 @@ bool LayerPlan::writePathWithCoasting( prev_pt = path.points[point_idx]; } - gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + writeExtrusionRelativeZ(gcode, start, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, start, extrude_speed); } From 3a8682b217e896c1a92185eb400a23a4be543dcf Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 31 Oct 2024 12:10:24 +0000 Subject: [PATCH 110/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9e613fadae..c1198d7f20 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From dbd79433d9fb347a3a7c4ddaee893b76096ca965 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 1 Nov 2024 11:36:19 +0100 Subject: [PATCH 111/139] Fix outer wall being translated CURA-12251 Instead of always reusing the results of the split segments destination, we now calculate them from the initial wall segment, avoiding some rounding errors being accumulated over time and translating the whole path significantly. --- src/LayerPlan.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index abd8540644..684ed5f2da 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1089,6 +1089,7 @@ void LayerPlan::addSplitWall( const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); const Point2LL& p1 = wall.pointAt(actual_point_index); const coord_t w1 = wall.lineWidthAt(actual_point_index); + coord_t segment_processed_distance = 0; if constexpr (std::is_same_v) { @@ -1191,7 +1192,8 @@ void LayerPlan::addSplitWall( // Now take the closest position candidate and make a sub-segment to it const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); const coord_t length_to_process = destination_position - wall_processed_distance; - Point3LL split_destination = split_origin + normal(line_vector, length_to_process); + const double destination_factor = static_cast(segment_processed_distance + length_to_process) / line_length; + Point3LL split_destination = cura::lerp(p0, p1, destination_factor); double scarf_segment_flow_ratio = 1.0; double scarf_factor_destination = 1.0; // Out of range, scarf is done => 1.0 @@ -1258,6 +1260,7 @@ void LayerPlan::addSplitWall( distance_to_bridge_start); wall_processed_distance = destination_position; + segment_processed_distance += length_to_process; piece_remaining_distance -= length_to_process; split_origin = split_destination; scarf_factor_origin = scarf_factor_destination; @@ -1288,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 03116d9dd8c950c9b25517c14c91cbcab4d256d6 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 1 Nov 2024 10:36:51 +0000 Subject: [PATCH 112/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 684ed5f2da..ff345c7089 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1291,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From d68cc1160ba3482e4f96068cf007185d9433a81c Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 1 Nov 2024 12:53:44 +0100 Subject: [PATCH 113/139] Fix build, broken with conflict resolving durimng merge CURA-12244 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7856d9f393..df174b4fc4 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1288,7 +1288,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2961,7 +2961,7 @@ bool LayerPlan::writePathWithCoasting( previous_position = path.points[point_idx]; } - writeExtrusionRelativeZ(gcode, start, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + writeExtrusionRelativeZ(gcode, path_coasting.coasting_start_pos, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, path_coasting.coasting_start_pos, extrude_speed); } From 71e058c1834450f89be28f5e4c4b93bd05fdb315 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 1 Nov 2024 11:54:12 +0000 Subject: [PATCH 114/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index df174b4fc4..e38d98edd7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1288,7 +1288,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From bc50df1bf6d4bacdc2870e5a43f8c9b386c70d13 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 4 Nov 2024 12:57:33 +0100 Subject: [PATCH 115/139] Bump grpc_definitions version for 5.9 CURA-12259 We made some significant changes between 5.8 and 5.9, so now is the time to bump the version number in order to indicate this. --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index a44c2c758b..cf5986eed1 100644 --- a/conandata.yml +++ b/conandata.yml @@ -5,6 +5,6 @@ requirements: requirements_arcus: - "arcus/5.4.1" requirements_plugins: - - "curaengine_grpc_definitions/0.2.1" + - "curaengine_grpc_definitions/0.3.0" requirements_cura_resources: - "cura_resources/5.9.0-beta.1" From e091983884b323462c4de3169b944ff0e41c04fc Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 4 Nov 2024 15:59:00 +0100 Subject: [PATCH 116/139] Partially fix combing issue CURA-12234 Add a small offset while computing the combing area, in order to avoid zero-width polygons when doing difference. --- src/LayerPlan.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index bb2eccf524..98b1465677 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -190,14 +190,16 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) { continue; } + + constexpr coord_t extra_offset = 10; // Additional offset to avoid zero-width polygons remains coord_t offset; switch (boundary_type) { case CombBoundary::MINIMUM: - offset = -mesh.settings.get("machine_nozzle_size") / 2 - mesh.settings.get("wall_line_width_0") / 2; + offset = -mesh.settings.get("machine_nozzle_size") / 2 - mesh.settings.get("wall_line_width_0") / 2 - extra_offset; break; case CombBoundary::PREFERRED: - offset = -mesh.settings.get("machine_nozzle_size") * 3 / 2 - mesh.settings.get("wall_line_width_0") / 2; + offset = -mesh.settings.get("machine_nozzle_size") * 3 / 2 - mesh.settings.get("wall_line_width_0") / 2 - extra_offset; break; default: offset = 0; @@ -227,6 +229,7 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) top_and_bottom_most_fill.push_back(skin_part.bottom_most_surface_fill); } } + comb_boundary.push_back(part.outline.offset(offset).difference(top_and_bottom_most_fill)); } else if (combing_mode == CombingMode::INFILL) // Add the infill (infill only) @@ -1291,7 +1294,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 3e5e1022b39639b2bd7ba2ccaa4059b5e52f562a Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Mon, 4 Nov 2024 14:59:30 +0000 Subject: [PATCH 117/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 98b1465677..7a2cb4a3de 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1294,7 +1294,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From f684de325f5d50d1a48135e06a580bec42c3889c Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 5 Nov 2024 13:05:13 +0000 Subject: [PATCH 118/139] Set conan package version 5.9.0-beta.2 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index cf5986eed1..28abea3228 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-beta.1" +version: "5.9.0-beta.2" commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" @@ -7,4 +7,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.3.0" requirements_cura_resources: - - "cura_resources/5.9.0-beta.1" + - "cura_resources/5.9.0-beta.2" From 0948be12be131fb28611181290892e3f34bb6ee2 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 5 Nov 2024 15:20:47 +0100 Subject: [PATCH 119/139] Fix dirty print lines and micro-segments CURA-12263 --- src/LayerPlan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index bb2eccf524..6f8fdbdc29 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1271,6 +1271,7 @@ void LayerPlan::addSplitWall( } p0 = p1; + w0 = w1; } } From a6450b00d2c7e030d308bc07e16ee11b8355b18b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 5 Nov 2024 16:31:15 +0100 Subject: [PATCH 120/139] Fix non-straight user-defined seam CURA-12264 With a high-definition model, the new vertex created especially for the seam is quite close to the other points of its former segment. So the two other vertices would end up at good enough outsiders for the distance criterion. Now the distance factor of the criterion is a setting, so that in this specific case we can change the value to give more weight to the specific vertex and less to others, even if very close. --- include/PathOrderOptimizer.h | 5 ++++- include/utils/scoring/DistanceScoringCriterion.h | 12 +++++++++++- src/utils/scoring/DistanceScoringCriterion.cpp | 9 +++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index ae01be4b25..a2e917fae8 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -740,7 +740,10 @@ class PathOrderOptimizer if (path.force_start_index_.has_value()) // Actually handles EZSeamType::USER_SPECIFIED { - main_criterion.criterion = std::make_shared(points, points.at(path.force_start_index_.value())); + // Use a much smaller distance divider because we want points around the forced points to be filtered out very easily + constexpr double distance_divider = 1.0; + constexpr auto distance_type = DistanceScoringCriterion::DistanceType::Euclidian; + main_criterion.criterion = std::make_shared(points, points.at(path.force_start_index_.value()), distance_type, distance_divider); } else { diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h index 3b6d8785d3..800e0a8c72 100644 --- a/include/utils/scoring/DistanceScoringCriterion.h +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -30,8 +30,18 @@ class DistanceScoringCriterion : public ScoringCriterion const Point2LL& target_pos_; const DistanceType distance_type_; + /*! + * Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + * distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + */ + const double distance_divider_; + public: - explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type = DistanceType::Euclidian); + explicit DistanceScoringCriterion( + const PointsSet& points, + const Point2LL& target_pos, + DistanceType distance_type = DistanceType::Euclidian, + const double distance_divider = 20.0); virtual double computeScore(const size_t candidate_index) const override; }; diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp index 0c68e875b8..d087162365 100644 --- a/src/utils/scoring/DistanceScoringCriterion.cpp +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -9,10 +9,11 @@ namespace cura { -DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type) +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type, const double distance_divider) : points_(points) , target_pos_(target_pos) , distance_type_(distance_type) + , distance_divider_(distance_divider) { } @@ -20,10 +21,6 @@ double DistanceScoringCriterion::computeScore(const size_t candidate_index) cons { const Point2LL& candidate_position = points_.at(candidate_index); - // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered - // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 - constexpr double distance_divider = 20.0; - double distance = 0.0; switch (distance_type_) { @@ -40,7 +37,7 @@ double DistanceScoringCriterion::computeScore(const size_t candidate_index) cons } // Use reciprocal function to normalize distance score decreasingly - return 1.0 / (1.0 + (distance / distance_divider)); + return 1.0 / (1.0 + (distance / distance_divider_)); } } // namespace cura From 1456b393affe18aa6978b9d5ac406fa9a4cfcb57 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 6 Nov 2024 19:20:22 +0100 Subject: [PATCH 121/139] Fix combing sometimes being processed over outer surfaces CURA-12234 Instead of computing the combing surfaces based on outer surfaces infill, use the full outer surfaces so that the outer walls are not considered for combing. --- include/skin.h | 9 ++------- include/sliceDataStorage.h | 4 ++-- src/LayerPlan.cpp | 34 +++++++++++++++------------------- src/skin.cpp | 38 +++++++++++++++++--------------------- 4 files changed, 36 insertions(+), 49 deletions(-) diff --git a/include/skin.h b/include/skin.h index fe7d555ef7..558ca59f64 100644 --- a/include/skin.h +++ b/include/skin.h @@ -135,14 +135,9 @@ class SkinInfillAreaComputation void generateRoofingFillAndSkinFill(SliceLayerPart& part); /*! - * Remove the areas which are directly under air in the top-most surface and directly above air in bottom-most - * surfaces from the \ref SkinPart::inner_infill and save them in the \ref SkinPart::bottom_most_surface_fill and - * \ref SkinPart::top_most_surface_fill (respectively) of the \p part. - * - * \param[in,out] part Where to get the SkinParts to get the outline info from and to store the top and bottom-most - * infill areas + * Generate the top and bottom-most surfaces of the given \p part, i.e. the surfaces that have nothing above or below */ - void generateTopAndBottomMostSkinFill(SliceLayerPart& part); + void generateTopAndBottomMostSurfaces(SliceLayerPart& part); /*! * Helper function to calculate and return the areas which are 'directly' under air. diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 4b434aa5ae..cb35355b4a 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -45,8 +45,6 @@ class SkinPart //!< roofing and non-roofing. Shape skin_fill; //!< The part of the skin which is not roofing. Shape roofing_fill; //!< The inner infill which has air directly above - Shape top_most_surface_fill; //!< The inner infill of the uppermost top layer which has air directly above. - Shape bottom_most_surface_fill; //!< The inner infill of the bottommost bottom layer which has air directly below. }; /*! @@ -69,6 +67,8 @@ class SliceLayerPart std::vector skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets. std::vector wall_toolpaths; //!< toolpaths for walls, will replace(?) the insets. Binned by inset_idx. std::vector infill_wall_toolpaths; //!< toolpaths for the walls of the infill areas. Binned by inset_idx. + Shape top_most_surface; //!< Sub-part of the outline containing the area that is not covered by something above + Shape bottom_most_surface; //!< Sub-part of the outline containing the area that has nothing below /*! * The areas inside of the mesh. diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 912d185d53..2e3848fec8 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -210,37 +210,33 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) const CombingMode combing_mode = mesh.settings.get("retraction_combing"); for (const SliceLayerPart& part : layer.parts) { - if (combing_mode == CombingMode::ALL) // Add the increased outline offset (skin, infill and part of the inner walls) + if (combing_mode == CombingMode::INFILL) { - comb_boundary.push_back(part.outline.offset(offset)); + comb_boundary = part.infill_area; } - else if (combing_mode == CombingMode::NO_SKIN) // Add the increased outline offset, subtract skin (infill and part of the inner walls) - { - comb_boundary.push_back(part.outline.offset(offset).difference(part.inner_area.difference(part.infill_area))); - } - else if (combing_mode == CombingMode::NO_OUTER_SURFACES) + else { - Shape top_and_bottom_most_fill; - for (const SliceLayerPart& outer_surface_part : layer.parts) + comb_boundary = part.outline.offset(offset); + + if (combing_mode == CombingMode::NO_SKIN) // Add the increased outline offset, subtract skin (infill and part of the inner walls) + { + comb_boundary = comb_boundary.difference(part.inner_area.difference(part.infill_area)); + } + else if (combing_mode == CombingMode::NO_OUTER_SURFACES) { - for (const SkinPart& skin_part : outer_surface_part.skin_parts) + for (const SliceLayerPart& outer_surface_part : layer.parts) { - top_and_bottom_most_fill.push_back(skin_part.top_most_surface_fill); - top_and_bottom_most_fill.push_back(skin_part.bottom_most_surface_fill); + comb_boundary = comb_boundary.difference(outer_surface_part.top_most_surface); + comb_boundary = comb_boundary.difference(outer_surface_part.bottom_most_surface); } } - - comb_boundary.push_back(part.outline.offset(offset).difference(top_and_bottom_most_fill)); - } - else if (combing_mode == CombingMode::INFILL) // Add the infill (infill only) - { - comb_boundary.push_back(part.infill_area); } } } break; } } + return comb_boundary; } @@ -1295,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) diff --git a/src/skin.cpp b/src/skin.cpp index 894692c815..987e39a8b9 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -93,7 +93,7 @@ void SkinInfillAreaComputation::generateSkinsAndInfill() { generateRoofingFillAndSkinFill(part); - generateTopAndBottomMostSkinFill(part); + generateTopAndBottomMostSurfaces(part); } } @@ -343,6 +343,22 @@ void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& p } } +void SkinInfillAreaComputation::generateTopAndBottomMostSurfaces(SliceLayerPart& part) +{ + const Shape outline_above = getOutlineOnLayer(part, layer_nr_ + 1); + part.top_most_surface = part.outline.difference(outline_above); + + if (layer_nr_ > 0) + { + const Shape outline_below = getOutlineOnLayer(part, layer_nr_ + 1); + part.bottom_most_surface = part.outline.difference(outline_below); + } + else + { + part.bottom_most_surface = part.outline; + } +} + /* * This function is executed in a parallel region based on layer_nr. * When modifying make sure any changes does not introduce data races. @@ -633,24 +649,4 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) } } -/* - * This function is executed in a parallel region based on layer_nr. - * When modifying make sure any changes does not introduce data races. - * - * this function may only read/write the skin and infill from the *current* layer. - */ - -void SkinInfillAreaComputation::generateTopAndBottomMostSkinFill(SliceLayerPart& part) -{ - for (SkinPart& skin_part : part.skin_parts) - { - Shape filled_area_above = generateFilledAreaAbove(part, 1); - skin_part.top_most_surface_fill = skin_part.outline.difference(filled_area_above); - - Shape filled_area_below = generateFilledAreaBelow(part, 1); - skin_part.bottom_most_surface_fill = skin_part.skin_fill.difference(filled_area_below); - } -} - - } // namespace cura From c6faf2ae11ff089e6060eee8a0ad50e915dc97b9 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 6 Nov 2024 18:21:06 +0000 Subject: [PATCH 122/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2e3848fec8..df37dde032 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1291,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 643a7124ed418a0435423eb788fc6bc0aeba5c8c Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 7 Nov 2024 15:31:12 +0100 Subject: [PATCH 123/139] Fix useless Z move before switching layer CURA-12268 This was introduced while moving positions to 3D points. The Z offset of the travel move now has to be correct, otherwise we will move the nozzle up for next layer, then move back down for the travel move, then move up again when actually starting to print. --- src/LayerPlan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 6f8fdbdc29..b71356e5f3 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2748,6 +2748,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeTravel(current_position, extruder.settings_.get("speed_z_hop")); // Prevent the final travel(s) from resetting to the 'previous' layer height. + path.z_offset = final_travel_z_ - z_; gcode.setZ(final_travel_z_); } for (size_t point_idx = 0; point_idx + 1 < path.points.size(); point_idx++) From cae1d6ed8dd193ce3a9e0cbc07a783858bb3115c Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 8 Nov 2024 09:21:42 +0100 Subject: [PATCH 124/139] Fix broken combing CURA-12250 The previous refactoring left only the boundary of the last processed part, now we use the boundaries of all the parts again. --- src/LayerPlan.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index df37dde032..4c21a154b6 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -210,27 +210,31 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) const CombingMode combing_mode = mesh.settings.get("retraction_combing"); for (const SliceLayerPart& part : layer.parts) { + Shape part_combing_boundary; + if (combing_mode == CombingMode::INFILL) { - comb_boundary = part.infill_area; + part_combing_boundary = part.infill_area; } else { - comb_boundary = part.outline.offset(offset); + part_combing_boundary = part.outline.offset(offset); if (combing_mode == CombingMode::NO_SKIN) // Add the increased outline offset, subtract skin (infill and part of the inner walls) { - comb_boundary = comb_boundary.difference(part.inner_area.difference(part.infill_area)); + part_combing_boundary = part_combing_boundary.difference(part.inner_area.difference(part.infill_area)); } else if (combing_mode == CombingMode::NO_OUTER_SURFACES) { for (const SliceLayerPart& outer_surface_part : layer.parts) { - comb_boundary = comb_boundary.difference(outer_surface_part.top_most_surface); - comb_boundary = comb_boundary.difference(outer_surface_part.bottom_most_surface); + part_combing_boundary = part_combing_boundary.difference(outer_surface_part.top_most_surface); + part_combing_boundary = part_combing_boundary.difference(outer_surface_part.bottom_most_surface); } } } + + comb_boundary.push_back(part_combing_boundary); } } break; @@ -1291,7 +1295,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 3c73ce8a98f30ad98e9c14071f3d3ab0be98a3ed Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 8 Nov 2024 08:22:17 +0000 Subject: [PATCH 125/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 4c21a154b6..f7b9875b31 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1295,7 +1295,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 13cc38976c65b5488555ed6841360684bd052805 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 8 Nov 2024 12:25:01 +0100 Subject: [PATCH 126/139] Take care of reverted paths when adding wipe move CURA-12265 --- src/LayerPlan.cpp | 64 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 6f8fdbdc29..b940efe2bd 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1292,7 +1292,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -1625,30 +1625,54 @@ void LayerPlan::addWall( if (wall_0_wipe_dist > 0 && ! is_linked_path) { // apply outer wall wipe - ExtrusionJunction p0 = wall[start_idx]; - coord_t distance_traversed = 0; - for (unsigned int point_idx = 1;; point_idx++) + const auto add_wipe = [this, &wall_0_wipe_dist](const auto begin, const auto end, const auto start) { - if (point_idx > wall.size() && distance_traversed == 0) // Wall has a total circumference of 0. This loop would never end. - { - break; // No wipe if the wall has no circumference. - } - ExtrusionJunction p1 = wall[(start_idx + point_idx) % wall.size()]; - coord_t p0p1_dist = vSize(p1 - p0); - if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) - { - Point2LL vector = p1.p_ - p0.p_; - Point2LL half_way = p0.p_ + normal(vector, wall_0_wipe_dist - distance_traversed); - addTravel_simple(half_way); - break; - } - else + auto iterator = start; + ExtrusionJunction p0 = *iterator; + coord_t distance_traversed = 0; + + while (distance_traversed < wall_0_wipe_dist) { - addTravel_simple(p1.p_); + ++iterator; + if (iterator == end) + { + if (distance_traversed == 0) + { + // Wall has a total circumference of 0. This loop would never end. + break; + } + + iterator = begin; // Loop until we have reached the wipe distance + } + + ExtrusionJunction p1 = *iterator; + coord_t p0p1_dist = vSize(p1 - p0); + if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) + { + Point2LL vector = p1.p_ - p0.p_; + Point2LL half_way = p0.p_ + normal(vector, wall_0_wipe_dist - distance_traversed); + addTravel_simple(half_way); + } + else + { + addTravel_simple(p1.p_); + } + distance_traversed += p0p1_dist; + p0 = p1; } - p0 = p1; + }; + + const auto iterator_start = wall.begin() + start_idx; + if (is_reversed) + { + add_wipe(wall.rbegin(), wall.rend(), std::make_reverse_iterator(iterator_start + 1)); } + else + { + add_wipe(wall.begin(), wall.end(), iterator_start); + } + forceNewPathStart(); } } From 5ba969f11caf661204dec18b0826a93db9abd771 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 8 Nov 2024 11:25:30 +0000 Subject: [PATCH 127/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b940efe2bd..4997541944 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1292,7 +1292,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 2a0670620453e27bbe6815ea4547cf8bc9bcae80 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 8 Nov 2024 14:19:01 +0100 Subject: [PATCH 128/139] Apply wipe distance with a single method CURA-12265 Previously, there were two different implementations of the same feature, for Polygons and ExtrusionLines. We now have a single method to handle this for the two types, avoiding different behaviors. --- include/LayerPlan.h | 15 ++++- src/LayerPlan.cpp | 137 +++++++++++++++++--------------------------- 2 files changed, 68 insertions(+), 84 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 8652e3d584..a9837db372 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -926,9 +926,10 @@ class LayerPlan : public NoCopy * \param scarf_seam Indicates whether we may use a scarf seam for the path * \param smooth_speed Indicates whether we may use a speed gradient for the path * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + * \return The actual length of the added scarf seam */ template - void addWallWithScarfSeam( + coord_t addWallWithScarfSeam( const PathAdapter& wall, size_t start_idx, const Settings& settings, @@ -942,6 +943,18 @@ class LayerPlan : public NoCopy const bool smooth_speed, const AddExtrusionSegmentFunction& func_add_segment); + /*! + * \brief Add a wipe travel after the given path has been extruded + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param path The path that has just been extruded + * \param wipe_distance The length of the wipe move to be added + * \param backwards Indicates if the path has been processed backwards + * \param start_index The index of the point where o start printing the path + * \param scarf_seam_length The length of the scarf seat that has been added to the path + */ + template + void addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const coord_t scarf_seam_length); + /*! * Pre-calculates the coasting to be applied on the paths * diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b940efe2bd..61aabcf759 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -554,6 +554,45 @@ void LayerPlan::addExtrusionMove( last_planned_position_ = p.toPoint2LL(); } +template +void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const coord_t scarf_seam_length) +{ + if (path.size() >= 2 && wipe_distance > 0) + { + const int direction = backwards ? -1 : 1; + Point2LL p0 = path.pointAt(start_index); + int distance_traversed = 0; + size_t index = start_index; + while (distance_traversed < wipe_distance) + { + index = static_cast((index + direction + path.size()) % path.size()); + if (index == start_index && distance_traversed == 0) + { + // Wall has a total circumference of 0. This loop would never end. + break; + } + + const Point2LL& p1 = path.pointAt(index); + const int p0p1_dist = vSize(p1 - p0); + if (distance_traversed + p0p1_dist >= wipe_distance) + { + Point2LL vector = p1 - p0; + Point2LL half_way = p0 + normal(vector, wipe_distance - distance_traversed); + addTravel_simple(half_way); + } + else + { + addTravel_simple(p1); + } + + distance_traversed += p0p1_dist; + p0 = p1; + } + + forceNewPathStart(); + } +} + void LayerPlan::addPolygon( const Polygon& polygon, int start_idx, @@ -569,12 +608,13 @@ void LayerPlan::addPolygon( { constexpr bool is_closed = true; constexpr bool is_candidate_small_feature = false; + const PathAdapter path_adapter(polygon, config.getLineWidth()); Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); - addWallWithScarfSeam( - PathAdapter(polygon, config.getLineWidth()), + const coord_t scarf_seam_length = addWallWithScarfSeam( + path_adapter, start_idx, settings, config, @@ -602,31 +642,7 @@ void LayerPlan::addPolygon( if (polygon.size() > 2) { - if (wall_0_wipe_dist > 0) - { // apply outer wall wipe - p0 = polygon[start_idx]; - const int direction = backwards ? -1 : 1; - int distance_traversed = 0; - for (size_t point_idx = 1;; point_idx++) - { - Point2LL p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()]; - int p0p1_dist = vSize(p1 - p0); - if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) - { - Point2LL vector = p1 - p0; - Point2LL half_way = p0 + normal(vector, wall_0_wipe_dist - distance_traversed); - addTravel_simple(half_way); - break; - } - else - { - addTravel_simple(p1); - distance_traversed += p0p1_dist; - } - p0 = p1; - } - forceNewPathStart(); - } + addWipeTravel(path_adapter, wall_0_wipe_dist, backwards, start_idx, scarf_seam_length); } else { @@ -1460,7 +1476,7 @@ coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const } template -void LayerPlan::addWallWithScarfSeam( +coord_t LayerPlan::addWallWithScarfSeam( const PathAdapter& wall, size_t start_idx, const Settings& settings, @@ -1476,7 +1492,7 @@ void LayerPlan::addWallWithScarfSeam( { if (wall.empty()) { - return; + return 0; } const bool actual_scarf_seam = scarf_seam && is_closed; @@ -1496,7 +1512,7 @@ void LayerPlan::addWallWithScarfSeam( const int direction = is_reversed ? -1 : 1; const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); - const auto scarf_seam_length = std::min(wall_length, actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const coord_t scarf_seam_length = std::min(wall_length, actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; const auto scarf_split_distance = settings.get("scarf_split_distance"); const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); @@ -1554,6 +1570,8 @@ void LayerPlan::addWallWithScarfSeam( // Second pass to add the scarf closure addSplitWallPass(true); } + + return scarf_seam_length; } void LayerPlan::addWall( @@ -1579,9 +1597,10 @@ void LayerPlan::addWall( double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + const PathAdapter path_adapter(wall); - addWallWithScarfSeam( - PathAdapter(wall), + const coord_t scarf_seam_length = addWallWithScarfSeam( + path_adapter, start_idx, settings, default_config, @@ -1623,57 +1642,9 @@ void LayerPlan::addWall( computeDistanceToBridgeStart(wall, (start_idx + wall.size() - 1) % wall.size(), min_bridge_line_len); } - if (wall_0_wipe_dist > 0 && ! is_linked_path) - { // apply outer wall wipe - const auto add_wipe = [this, &wall_0_wipe_dist](const auto begin, const auto end, const auto start) - { - auto iterator = start; - ExtrusionJunction p0 = *iterator; - coord_t distance_traversed = 0; - - while (distance_traversed < wall_0_wipe_dist) - { - ++iterator; - if (iterator == end) - { - if (distance_traversed == 0) - { - // Wall has a total circumference of 0. This loop would never end. - break; - } - - iterator = begin; // Loop until we have reached the wipe distance - } - - ExtrusionJunction p1 = *iterator; - coord_t p0p1_dist = vSize(p1 - p0); - if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) - { - Point2LL vector = p1.p_ - p0.p_; - Point2LL half_way = p0.p_ + normal(vector, wall_0_wipe_dist - distance_traversed); - addTravel_simple(half_way); - } - else - { - addTravel_simple(p1.p_); - } - - distance_traversed += p0p1_dist; - p0 = p1; - } - }; - - const auto iterator_start = wall.begin() + start_idx; - if (is_reversed) - { - add_wipe(wall.rbegin(), wall.rend(), std::make_reverse_iterator(iterator_start + 1)); - } - else - { - add_wipe(wall.begin(), wall.end(), iterator_start); - } - - forceNewPathStart(); + if (! is_linked_path) + { + addWipeTravel(path_adapter, wall_0_wipe_dist, is_reversed, start_idx, scarf_seam_length); } } else From ab4b3efc7088b754adca48965fe7ebd0edd34850 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 8 Nov 2024 16:19:37 +0100 Subject: [PATCH 129/139] Take care of the actual final position to add wipe movement CURA-12265 With the addition of the scarf seam, printing an extrusion line now may end at a different position that the last point on the line, so the wipe travel should start at this position. --- include/LayerPlan.h | 11 +++++----- src/LayerPlan.cpp | 51 +++++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index a9837db372..387d1e9564 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -881,9 +881,10 @@ class LayerPlan : public NoCopy * \param compute_distance_to_bridge_start Whether we should compute the distance to start of bridge. This is * possible only if PathType is ExtrusionLine and will be ignored otherwise. * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + * \return The index of the last traversed point, and the final position with the scarf seam */ template - void addSplitWall( + std::tuple addSplitWall( const PathAdapter& wall, const coord_t wall_length, const size_t start_idx, @@ -926,10 +927,10 @@ class LayerPlan : public NoCopy * \param scarf_seam Indicates whether we may use a scarf seam for the path * \param smooth_speed Indicates whether we may use a speed gradient for the path * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters - * \return The actual length of the added scarf seam + * \return The index of the last traversed point, and the final position with the scarf seam */ template - coord_t addWallWithScarfSeam( + std::tuple addWallWithScarfSeam( const PathAdapter& wall, size_t start_idx, const Settings& settings, @@ -950,10 +951,10 @@ class LayerPlan : public NoCopy * \param wipe_distance The length of the wipe move to be added * \param backwards Indicates if the path has been processed backwards * \param start_index The index of the point where o start printing the path - * \param scarf_seam_length The length of the scarf seat that has been added to the path + * \param last_path_position The actual last position of the extruder, which may be slightly forwards on the last printed segment */ template - void addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const coord_t scarf_seam_length); + void addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position); /*! * Pre-calculates the coasting to be applied on the paths diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 61aabcf759..8b58cd3826 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -555,12 +555,12 @@ void LayerPlan::addExtrusionMove( } template -void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const coord_t scarf_seam_length) +void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position) { if (path.size() >= 2 && wipe_distance > 0) { const int direction = backwards ? -1 : 1; - Point2LL p0 = path.pointAt(start_index); + Point2LL p0 = last_path_position; int distance_traversed = 0; size_t index = start_index; while (distance_traversed < wipe_distance) @@ -613,7 +613,7 @@ void LayerPlan::addPolygon( Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); - const coord_t scarf_seam_length = addWallWithScarfSeam( + const std::tuple add_wall_result = addWallWithScarfSeam( path_adapter, start_idx, settings, @@ -642,7 +642,7 @@ void LayerPlan::addPolygon( if (polygon.size() > 2) { - addWipeTravel(path_adapter, wall_0_wipe_dist, backwards, start_idx, scarf_seam_length); + addWipeTravel(path_adapter, wall_0_wipe_dist, backwards, get<0>(add_wall_result), get<1>(add_wall_result)); } else { @@ -1055,7 +1055,7 @@ void LayerPlan::addWall( } template -void LayerPlan::addSplitWall( +std::tuple LayerPlan::addSplitWall( const PathAdapter& wall, const coord_t wall_length, const size_t start_idx, @@ -1099,10 +1099,14 @@ void LayerPlan::addSplitWall( double accelerate_factor_origin = 0.0; // Interpolation factor at the current point for the acceleration double decelerate_factor_origin = 0.0; // Interpolation factor at the current point for the deceleration const coord_t start_decelerate_position = wall_length - decelerate_length; + Point3LL split_destination = p0; + size_t previous_point_index = start_idx; + bool keep_processing = true; - for (size_t point_idx = 1; point_idx < max_index; point_idx++) + for (size_t point_idx = 1; point_idx < max_index && keep_processing; point_idx++) { const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); + previous_point_index = (wall.size() + start_idx + (point_idx - 1) * direction) % wall.size(); const Point2LL& p1 = wall.pointAt(actual_point_index); const coord_t w1 = wall.lineWidthAt(actual_point_index); coord_t segment_processed_distance = 0; @@ -1153,7 +1157,7 @@ void LayerPlan::addSplitWall( const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. const coord_t piece_length = round_divide(line_length, pieces); - for (size_t piece = 0; piece < pieces; ++piece) + for (size_t piece = 0; piece < pieces && keep_processing; ++piece) { const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values @@ -1171,7 +1175,7 @@ void LayerPlan::addSplitWall( coord_t piece_remaining_distance = piece_length; // Cut piece into smaller parts for scarf seam and acceleration/deceleration - while (piece_remaining_distance > 0 && (! is_scarf_closure || wall_processed_distance < scarf_seam_length)) + while (piece_remaining_distance > 0 && keep_processing) { // Make a list of all the possible incoming positions where we would eventually want to stop next // The positions are expressed in distance from wall start along the wall segments @@ -1209,7 +1213,7 @@ void LayerPlan::addSplitWall( const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); const coord_t length_to_process = destination_position - wall_processed_distance; const double destination_factor = static_cast(segment_processed_distance + length_to_process) / line_length; - Point3LL split_destination = cura::lerp(p0, p1, destination_factor); + split_destination = cura::lerp(p0, p1, destination_factor); double scarf_segment_flow_ratio = 1.0; double scarf_factor_destination = 1.0; // Out of range, scarf is done => 1.0 @@ -1282,6 +1286,11 @@ void LayerPlan::addSplitWall( scarf_factor_origin = scarf_factor_destination; accelerate_factor_origin = accelerate_factor_destination; decelerate_factor_origin = decelerate_factor_destination; + + if (is_scarf_closure) + { + keep_processing = wall_processed_distance < scarf_seam_length; + } } } } @@ -1289,6 +1298,8 @@ void LayerPlan::addSplitWall( p0 = p1; w0 = w1; } + + return { previous_point_index, split_destination.toPoint2LL() }; } std::vector @@ -1476,7 +1487,7 @@ coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const } template -coord_t LayerPlan::addWallWithScarfSeam( +std::tuple LayerPlan::addWallWithScarfSeam( const PathAdapter& wall, size_t start_idx, const Settings& settings, @@ -1492,10 +1503,10 @@ coord_t LayerPlan::addWallWithScarfSeam( { if (wall.empty()) { - return 0; + return { start_idx, Point2LL() }; } - const bool actual_scarf_seam = scarf_seam && is_closed; + const bool actual_scarf_seam = scarf_seam && is_closed && layer_nr_ > 0; const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); @@ -1529,11 +1540,11 @@ coord_t LayerPlan::addWallWithScarfSeam( const Velocity end_speed = top_speed * end_speed_ratio; // mm/s const coord_t decelerate_length = (smooth_speed && end_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(end_speed)) / (2.0 * deceleration)) : 0; // µm - auto addSplitWallPass = [&](bool is_scarf_closure) + auto addSplitWallPass = [&](bool is_scarf_closure) -> std::tuple { constexpr bool compute_distance_to_bridge_start = true; - addSplitWall( + return addSplitWall( PathAdapter(wall), wall_length, start_idx, @@ -1548,7 +1559,7 @@ coord_t LayerPlan::addWallWithScarfSeam( flow_ratio, nominal_line_width, min_bridge_line_len, - layer_nr_ > 0 ? scarf_seam_length : 0, + scarf_seam_length, scarf_seam_start_ratio, scarf_split_distance, scarf_max_z_offset, @@ -1563,15 +1574,15 @@ coord_t LayerPlan::addWallWithScarfSeam( }; // First pass to add the wall with the scarf beginning and acceleration - addSplitWallPass(false); + std::tuple result = addSplitWallPass(false); if (scarf_seam_length > 0) { // Second pass to add the scarf closure - addSplitWallPass(true); + result = addSplitWallPass(true); } - return scarf_seam_length; + return result; } void LayerPlan::addWall( @@ -1599,7 +1610,7 @@ void LayerPlan::addWall( const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); const PathAdapter path_adapter(wall); - const coord_t scarf_seam_length = addWallWithScarfSeam( + const std::tuple add_wall_result = addWallWithScarfSeam( path_adapter, start_idx, settings, @@ -1644,7 +1655,7 @@ void LayerPlan::addWall( if (! is_linked_path) { - addWipeTravel(path_adapter, wall_0_wipe_dist, is_reversed, start_idx, scarf_seam_length); + addWipeTravel(path_adapter, wall_0_wipe_dist, is_reversed, get<0>(add_wall_result), get<1>(add_wall_result)); } } else From eca1e950825f6165a6307886596869a63aa8353e Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 11 Nov 2024 13:16:17 +0100 Subject: [PATCH 130/139] Fix travel move not being retracted CURA-12275 The travel_retract_before_outer_wall setting is settable per-mesh, but the global value was previously taken in this context. We now take the value of the setting, for the mesh we are moving to. --- include/ExtruderPlan.h | 5 +++++ include/LayerPlan.h | 5 +++++ src/ExtruderPlan.cpp | 14 ++++++++++++++ src/LayerPlan.cpp | 15 ++++++++++++++- src/LayerPlanBuffer.cpp | 19 +++++++++++++++---- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index cfd2070975..e4d301969a 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -124,6 +124,11 @@ class ExtruderPlan */ void applyBackPressureCompensation(const Ratio back_pressure_compensation); + /*! + * Gets the mesh being printed first on this plan + */ + std::shared_ptr findFirstPrintedMesh() const; + private: LayerIndex layer_nr_{ 0 }; //!< The layer number at which we are currently printing. bool is_initial_layer_{ false }; //!< Whether this extruder plan is printed on the very first layer (which might be raft) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 8652e3d584..5df397d9db 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -761,6 +761,11 @@ class LayerPlan : public NoCopy */ void applyGradualFlow(); + /*! + * Gets the mesh being printed first on this layer + */ + std::shared_ptr findFirstPrintedMesh() const; + private: /*! * \brief Compute the preferred or minimum combing boundary diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index b492abc23e..58a6bb3f86 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -70,4 +70,18 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe 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); } } + +std::shared_ptr ExtruderPlan::findFirstPrintedMesh() const +{ + for (const GCodePath& path : paths_) + { + if (path.mesh) + { + return path.mesh; + } + } + + return nullptr; +} + } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b71356e5f3..92fbbaf0a8 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1292,7 +1292,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -3086,6 +3086,19 @@ void LayerPlan::applyGradualFlow() } } +std::shared_ptr LayerPlan::findFirstPrintedMesh() const +{ + for (const ExtruderPlan& extruder_plan : extruder_plans_) + { + if (std::shared_ptr mesh = extruder_plan.findFirstPrintedMesh()) + { + return mesh; + } + } + + return nullptr; +} + LayerIndex LayerPlan::getLayerNr() const { return layer_nr_; diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index 426c077055..4001f0837c 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -99,10 +99,21 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; const Settings& extruder_settings = Application::getInstance().current_slice_->scene.extruders[prev_layer->extruder_plans_.back().extruder_nr_].settings_; prev_layer->setIsInside(new_layer_destination_state->second); - const bool force_retract = extruder_settings.get("retract_at_layer_change") - || (mesh_group_settings.get("travel_retract_before_outer_wall") - && (mesh_group_settings.get("inset_direction") == InsetDirection::OUTSIDE_IN - || mesh_group_settings.get("wall_line_count") == 1)); // Moving towards an outer wall. + + const bool travel_retract_before_outer_wall = mesh_group_settings.get("travel_retract_before_outer_wall"); + const bool retract_at_layer_change = extruder_settings.get("retract_at_layer_change"); + bool next_mesh_retract_before_outer_wall = false; + std::shared_ptr first_printed_mesh = newest_layer->findFirstPrintedMesh(); + if (! retract_at_layer_change && first_printed_mesh && travel_retract_before_outer_wall) + { + // Check whether we are moving toving towards an outer wall and it should be retracted + const Settings& mesh_settings = first_printed_mesh->settings; + const InsetDirection inset_direction = mesh_settings.get("inset_direction"); + const size_t wall_line_count = mesh_settings.get("wall_line_count"); + + next_mesh_retract_before_outer_wall = inset_direction == InsetDirection::OUTSIDE_IN || wall_line_count == 1; + } + const bool force_retract = retract_at_layer_change || next_mesh_retract_before_outer_wall; prev_layer->final_travel_z_ = newest_layer->z_; GCodePath& path = prev_layer->addTravel(first_location_new_layer, force_retract); if (force_retract && ! path.retract) From 3607b068e36037f946232a48232a4407fb6223ea Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Mon, 11 Nov 2024 12:16:55 +0000 Subject: [PATCH 131/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 92fbbaf0a8..cd7a216b2f 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1292,7 +1292,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From d3a029b87ddb8a469d2e918b2e189c00f94f28a9 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Tue, 12 Nov 2024 16:40:20 +0000 Subject: [PATCH 132/139] Set conan package version 5.9.0 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index 28abea3228..f0173dfd71 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-beta.2" +version: "5.9.0" commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" @@ -7,4 +7,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.3.0" requirements_cura_resources: - - "cura_resources/5.9.0-beta.2" + - "cura_resources/5.9.0" From e83320d066369a1546c608997909527f60e996e6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 13 Nov 2024 12:52:03 +0100 Subject: [PATCH 133/139] Avoid division-by-zero; set tip-layers always at least to 1. should fix sentry#CURAENGINE-Q2 --- include/TreeSupportSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index f74b9cffe3..1c4cab4fc6 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -39,7 +39,7 @@ struct TreeSupportSettings , maximum_move_distance((angle < TAU / 4) ? std::llround(tan(angle) * layer_height) : std::numeric_limits::max()) , maximum_move_distance_slow((angle_slow < TAU / 4) ? std::llround(tan(angle_slow) * layer_height) : std::numeric_limits::max()) , support_bottom_layers(mesh_group_settings.get("support_bottom_enable") ? round_divide(mesh_group_settings.get("support_bottom_height"), layer_height) : 0) - , tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)) + , tip_layers(std::max(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height), 1LL)) , // Ensure lines always stack nicely even if layer height is large diameter_angle_scale_factor(sin(mesh_group_settings.get("support_tree_branch_diameter_angle")) * layer_height / branch_radius) , max_to_model_radius_increase(mesh_group_settings.get("support_tree_max_diameter_increase_by_merges_when_support_to_model") / 2) From 65518201e4a540ab04f76afe810d4a10d30e3402 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 13 Nov 2024 12:54:09 +0100 Subject: [PATCH 134/139] Avoid division-by-zero; make sure max-overhang-speed is divisable. should fix sentry#CURAENGINE-4R --- src/TreeSupportTipGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 0a2af0e233..79ac54a51f 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -64,7 +64,7 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceMeshStorage& mesh, T const double support_overhang_angle = mesh.settings.get("support_angle"); const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * config_.layer_height) : std::numeric_limits::max(); - if (max_overhang_speed == 0) + if (max_overhang_speed < 2) { max_overhang_insert_lag_ = std::numeric_limits::max(); } From 880114df5f2f9e2e5d000d4e9ea7f0257ca7a36c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 13 Nov 2024 12:57:30 +0100 Subject: [PATCH 135/139] Avoid division-by-zero; pretend length is 1 if too small. should fix sentry#CURAENGINE-G8 --- src/utils/polygonUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 4b961d2bec..870fda7e0b 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1420,7 +1420,7 @@ void PolygonUtils::fixSelfIntersections(const coord_t epsilon, Shape& polygon) { const Point2LL& other = polygon[poly_idx][(point_idx + 1) % pathlen]; const Point2LL vec = LinearAlg2D::pointIsLeftOfLine(other, a, b) > 0 ? b - a : a - b; - const coord_t len = vSize(vec); + const coord_t len = std::max(vSize(vec), 1LL); pt.X += (-vec.Y * move_dist) / len; pt.Y += (vec.X * move_dist) / len; } From 83d4b26b60bd13c208fc7664c7d5cf0355c092fe Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 14 Nov 2024 10:17:52 +0100 Subject: [PATCH 136/139] Apply coasting only on walls CURA-12288 --- src/LayerPlan.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 927e4357f7..d140242353 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1308,7 +1308,7 @@ std::tuple LayerPlan::addSplitWall( std::vector LayerPlan::calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const { - std::vector path_coastings; + std::vector path_coastings; path_coastings.resize(paths.size()); if (extruder_settings.get("coasting_enable")) @@ -1322,11 +1322,22 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) { + if (reversed_chunk.empty()) + { + continue; + } + + const PrintFeatureType type = reversed_chunk.front().second.config.getPrintFeatureType(); + if (type != PrintFeatureType::OuterWall && type != PrintFeatureType::InnerWall) + { + continue; + } + double accumulated_volume = 0.0; bool chunk_coasting_point_reached = false; bool chunk_min_volume_reached = false; From 7dff0622921af87902fb945a093338b65f99ed8d Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 14 Nov 2024 09:18:28 +0000 Subject: [PATCH 137/139] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index d140242353..39f3e1b510 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1322,7 +1322,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 9f10ae0eb45529ec367e559ed0724a8b78fcb1b2 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 26 Nov 2024 11:00:56 +0100 Subject: [PATCH 138/139] Add worklfows for building npm package for curaengine --- .github/workflows/npm-package.yml | 100 + CuraEngineJS/package-lock.json | 3181 +++++++++++++++++++++++++++++ CuraEngineJS/package.json | 47 + 3 files changed, 3328 insertions(+) create mode 100644 .github/workflows/npm-package.yml create mode 100644 CuraEngineJS/package-lock.json create mode 100644 CuraEngineJS/package.json diff --git a/.github/workflows/npm-package.yml b/.github/workflows/npm-package.yml new file mode 100644 index 0000000000..239334ff4d --- /dev/null +++ b/.github/workflows/npm-package.yml @@ -0,0 +1,100 @@ +name: NPM package + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to build' + required: true + default: 'main' + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Sync pip requirements + run: curl -O https://raw.githubusercontent.com/Ultimaker/cura-workflows/main/.github/workflows/requirements-runner.txt + working-directory: .github/workflows + + - name: Setup Python and pip + uses: actions/setup-python@v4 + with: + python-version: 3.11.x + cache: pip + cache-dependency-path: .github/workflows/requirements-runner.txt + + - name: Install Python requirements and Create default Conan profile + run: pip install -r .github/workflows/requirements-runner.txt + + - name: Install Linux system requirements for building + run: | + mkdir runner_scripts + cd runner_scripts + curl -O https://raw.githubusercontent.com/Ultimaker/cura-workflows/main/runner_scripts/ubuntu_setup.sh + chmod +x ubuntu_setup.sh + sudo ./ubuntu_setup.sh + + - name: Setup pipeline caches + run: | + mkdir -p /home/runner/.conan/downloads + mkdir -p /home/runner/.conan/data + + - name: Create default Conan profile + run: conan profile new default --detect + + # FIXME: Once merged to main: conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b NP-419" + + - name: Add runner credentials to cura remote + run: conan user -p ${{ secrets.CONAN_PASS }} -r cura ${{ secrets.CONAN_USER }} + + - name: Cache Conan packages + uses: actions/cache@v3 + with: + path: /home/runner/.conan/data + key: ${{ runner.os }}-conan-data-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-conan-data- + + - name: Cache Conan downloads + uses: actions/cache@v3 + with: + path: /home/runner/.conan/downloads + key: ${{ runner.os }}-conan-downloads-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-conan-downloads- + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + registry-url: 'https://npm.pkg.github.com' + scope: '@ultimaker' + + - name: Set npm config + run: | + npm run install_curaengine + npm ci + npm publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload the Package(s) + if: ${{ always() }} + run: | + conan remove "cura_private_data/*" --force + conan remove "fdm_materials/*" --force + conan upload "*" -r cura --all -c \ No newline at end of file diff --git a/CuraEngineJS/package-lock.json b/CuraEngineJS/package-lock.json new file mode 100644 index 0000000000..f304245dd9 --- /dev/null +++ b/CuraEngineJS/package-lock.json @@ -0,0 +1,3181 @@ +{ + "name": "@ultimaker/curaenginejs", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ultimaker/curaenginejs", + "version": "0.1.0", + "dependencies": { + "emscripten": "^0.0.2-beta", + "observable-fns": "^0.6.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@types/emscripten": "^1.39.10", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "eslint": "^8.55.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "typescript": "^5.2.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emscripten": { + "version": "0.0.2-beta", + "resolved": "https://registry.npmjs.org/emscripten/-/emscripten-0.0.2-beta.tgz", + "integrity": "sha512-bckLzZ9n/GhNB4NwE23frASTc0hB7EyrY9ykys+Ou5wKupOROtEzkeWqxoGmWYQVNbidSW5UYsHwo8hhCuqbKg==", + "hasInstallScript": true, + "bin": { + "emscripten": "index.js" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/observable-fns": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.6.1.tgz", + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "peer": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} \ No newline at end of file diff --git a/CuraEngineJS/package.json b/CuraEngineJS/package.json new file mode 100644 index 0000000000..2b50c6e9d9 --- /dev/null +++ b/CuraEngineJS/package.json @@ -0,0 +1,47 @@ +{ + "name": "@ultimaker/curaenginejs", + "version": "0.2.0", + "description": "CuraEngineJS a TS component to run CuraEngine in a browser", + "main": "src/CuraEngine.js", + "scripts": { + "install_curaengine": "conan install ${npm_package_config_conan_package} -s build_type=Release --build=missing --update -c tools.build:skip_test=True -pr:h cura_wasm.jinja -if src && rm -f src/*conan*", + "build": "npm run install_curaengine" + }, + "config": { + "conan_package": "curaengine/5.9.0@_/_" + }, + "repository": { + "type": "git", + "url": "https://github.com/Ultimaker/CuraEngine.git" + }, + "keywords": [ + "Cura", + "CuraEngine", + "Slicer" + ], + "author": "UltiMaker", + "license": "", + "bugs": { + "url": "https://github.com/Ultimaker/CuraEngine/issues" + }, + "homepage": "https://github.com/Ultimaker/CuraEngine#readme", + "dependencies": { + "emscripten": "^0.0.2-beta", + "observable-fns": "^0.6.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@types/emscripten": "^1.39.10", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "eslint": "^8.55.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "typescript": "^5.2.2" + }, + "files": [ + "dist", + "package.json", + "README.md" + ] +} \ No newline at end of file From d77a2a49ea4353dbd6e5eec29babbc78d0144258 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 26 Nov 2024 11:35:44 +0100 Subject: [PATCH 139/139] Fix gcode-export test after added settings values. --- tests/GCodeExportTest.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/GCodeExportTest.cpp b/tests/GCodeExportTest.cpp index 5a631e98a9..5cbf0b1570 100644 --- a/tests/GCodeExportTest.cpp +++ b/tests/GCodeExportTest.cpp @@ -449,6 +449,8 @@ TEST_F(GCodeExportTest, SwitchExtruderSimple) scene.extruders.emplace_back(0, nullptr); ExtruderTrain& train1 = scene.extruders.back(); + train1.settings_.add("machine_extruder_prestart_code", ";PRESTART FIRST EXTRUDER"); + train1.settings_.add("machine_extruder_change_duration", "10.0"); train1.settings_.add("machine_extruder_start_code", ";FIRST EXTRUDER START G-CODE!"); train1.settings_.add("machine_extruder_end_code", ";FIRST EXTRUDER END G-CODE!"); train1.settings_.add("machine_extruder_start_code_duration", "0.0"); @@ -459,6 +461,8 @@ TEST_F(GCodeExportTest, SwitchExtruderSimple) scene.extruders.emplace_back(1, nullptr); ExtruderTrain& train2 = scene.extruders.back(); + train2.settings_.add("machine_extruder_prestart_code", ";PRESTART SECOND EXTRUDER"); + train2.settings_.add("machine_extruder_change_duration", "11.1"); train2.settings_.add("machine_extruder_start_code", ";SECOND EXTRUDER START G-CODE!"); train2.settings_.add("machine_extruder_end_code", ";SECOND EXTRUDER END G-CODE!"); train2.settings_.add("machine_extruder_start_code_duration", "0.0"); @@ -473,7 +477,7 @@ TEST_F(GCodeExportTest, SwitchExtruderSimple) EXPECT_CALL(*mock_communication, sendCurrentPosition(testing::_)); gcode.switchExtruder(1, no_retraction); - EXPECT_EQ(std::string("G92 E0\n;FIRST EXTRUDER END G-CODE!\nT1\nG92 E0\n;SECOND EXTRUDER START G-CODE!\n"), output.str()); + EXPECT_EQ(std::string("G92 E0\n;FIRST EXTRUDER END G-CODE!\n;PRESTART SECOND EXTRUDER\nT1\nG92 E0\n;SECOND EXTRUDER START G-CODE!\n"), output.str()); } TEST_F(GCodeExportTest, WriteZHopStartZero)