From b5f2e81e94f1546c9a888a44bcc44881d3857f8a Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Sun, 7 Apr 2024 18:07:50 +0200 Subject: [PATCH 01/60] Fix initial layer diameter not working --- include/TreeSupportSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index b6b38dab4a..88bb51c391 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -50,7 +50,7 @@ struct TreeSupportSettings : RestPreference::BUILDPLATE) , xy_distance(mesh_group_settings.get("support_xy_distance")) , bp_radius(mesh_group_settings.get("support_tree_bp_diameter") / 2) - , diameter_scale_bp_radius(std::min(sin(0.7) * static_cast(layer_height / branch_radius), 1.0 / (branch_radius / (support_line_width / 2.0)))) + , diameter_scale_bp_radius(std::min(sin(0.7) * static_cast(layer_height) / static_cast(branch_radius), 1.0 / (branch_radius / (support_line_width / 2.0)))) , // Either 40° or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. support_overrides(mesh_group_settings.get("support_xy_overrides_z")) , xy_min_distance(support_overrides == SupportDistPriority::Z_OVERRIDES_XY ? mesh_group_settings.get("support_xy_distance_overhang") : xy_distance) From 5327c0286f1421e1f44aba57e58736acc3c77239 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 15 Jul 2024 11:53:53 +0200 Subject: [PATCH 02/60] Add ability to register multiple modify plugins The plugins will be called one after the other, in the order they were registered. CURA-10914 --- include/plugins/slotproxy.h | 34 +++++++++++++++++++++------------- include/plugins/slots.h | 3 +-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/include/plugins/slotproxy.h b/include/plugins/slotproxy.h index 82dd57d2a5..a156286295 100644 --- a/include/plugins/slotproxy.h +++ b/include/plugins/slotproxy.h @@ -40,7 +40,7 @@ class SlotProxy { Default default_process{}; using value_type = PluginProxy; - std::optional plugin_{ std::nullopt }; + std::vector plugins_; public: static constexpr plugins::v0::SlotID slot_id{ SlotID }; @@ -53,14 +53,15 @@ class SlotProxy SlotProxy() noexcept = default; /** - * @brief Constructs a SlotProxy object with a plugin. - * - * Constructs a SlotProxy object and initializes the plugin using the provided gRPC channel. - * + * @brief Adds a plugin to this proxy. + * @param name The fully-qualified name of the plugin + * @param version The full verison of the plugin * @param channel A shared pointer to the gRPC channel for communication with the plugin. */ - SlotProxy(const std::string& name, const std::string& version, std::shared_ptr channel) - : plugin_{ value_type{ name, version, channel } } {}; + void addPlugin(const std::string& name, const std::string& version, std::shared_ptr channel) + { + plugins_.emplace_back(name, version, channel); + } /** * @brief Executes the plugin operation. @@ -75,18 +76,25 @@ class SlotProxy */ constexpr auto generate(auto&&... args) { - if (plugin_.has_value()) + if (! plugins_.empty()) { - return plugin_.value().generate(std::forward(args)...); + return plugins_.front().generate(std::forward(args)...); } return std::invoke(default_process, std::forward(args)...); } constexpr auto modify(auto& original_value, auto&&... args) { - if (plugin_.has_value()) + if (! plugins_.empty()) { - return plugin_.value().modify(original_value, std::forward(args)...); + auto modified_value = original_value; + + for (value_type& plugin : plugins_) + { + modified_value = plugin.modify(modified_value, std::forward(args)...); + } + + return modified_value; } if constexpr (sizeof...(args) == 0) { @@ -98,9 +106,9 @@ class SlotProxy template void broadcast(auto&&... args) { - if (plugin_.has_value()) + for (value_type& plugin : plugins_) { - plugin_.value().template broadcast(std::forward(args)...); + plugin.template broadcast(std::forward(args)...); } } }; diff --git a/include/plugins/slots.h b/include/plugins/slots.h index 30740be3c4..515a1a41ab 100644 --- a/include/plugins/slots.h +++ b/include/plugins/slots.h @@ -160,8 +160,7 @@ class Registry, Unit> : public Registry { if (slot_id == T::slot_id) { - using Tp = typename Unit::value_type; - value_.proxy = Tp{ name, version, std::forward(channel) }; + value_.proxy.addPlugin(name, version, std::forward(channel)); return; } Base::connect(slot_id, name, version, std::forward(channel)); From 65c344407478fe4c8de98e873ce28a663693f7c8 Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Thu, 18 Jul 2024 12:26:48 +0100 Subject: [PATCH 03/60] Create extra lines in infill to support skins above --- src/FffGcodeWriter.cpp | 166 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 64d52a93a4..ad1160104b 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1991,6 +1991,57 @@ bool FffGcodeWriter::processMultiLayerInfill( return added_something; } +// Return a set of parallel lines at a given angle within an area +// which cover a set of points +void getLinesForArea(OpenLinesSet& result_lines, const Shape& area, const AngleDegrees& angle, const PointsSet& points, coord_t line_width) +{ + OpenLinesSet candidate_lines; + Shape unused_skin_polygons; + std::vector unused_skin_paths; + + // We just want a set of lines which cover a Shape, and reusing this + // code seems like the best way. + Infill infill_comp(EFillMethod::LINES, false, false, area, line_width, line_width, 0, 1, angle, 0, 0, 0, 0); + + infill_comp.generate(unused_skin_paths, unused_skin_polygons, candidate_lines, {}, 0, SectionType::SKIN); + + // Select only lines which are needed to support points + for (const auto& line : candidate_lines) + { + const Shape line_shape = OpenLinesSet(line).offset(line_width / 2); + for (const auto& point : points) + { + if (line_shape.inside(point)) + { + result_lines.push_back(line); + break; + } + } + } +} + +// Return a set of parallel lines within an area which +// fully support (cover) a set of points. +void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& area, const PointsSet& points, coord_t line_width) +{ + OpenLinesSet candidate_lines; + + const int numAngles = 16; + const double angleStep = 180.0 / numAngles; // Step size between angles + + for (int i = 0; i < numAngles; ++i) + { + const AngleDegrees angle{ i * angleStep }; + candidate_lines.clear(); + getLinesForArea(candidate_lines, area, angle, points, line_width); + if (candidate_lines.length() < result_lines.length() || result_lines.length() == 0) + { + result_lines = candidate_lines; + } + } +} + + bool FffGcodeWriter::processSingleLayerInfill( const SliceDataStorage& storage, LayerPlan& gcode_layer, @@ -2235,6 +2286,111 @@ bool FffGcodeWriter::processSingleLayerInfill( infill_polygons.push_back(infill_polygons_here); } + /* Create a set of extra lines to support skins above. + * + * Skins above need their boundaries supported at a minimum. + * A straight line needs support just at the ends. + * A curve needs support at various points along the curve. + * We find these points by finding the minimal polygon which is + * always within line_width of the desired support. + * + * The strategy here is to figure out what is currently printed on + * this layer within the infill area by taking all currently printed + * lines and turning them into a giant hole-y shape. + * + * Then figure out extra lines to add to support all points + * that are unsupported. The extra lines will always be straight + * and will always go between existing infill lines. + */ + + OpenLinesSet support_lines; + support_lines.push_back(infill_lines); + for (const auto& poly : part.getOwnInfillArea()) + { + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + + // We just want to grab all lines out of this datastructure + for (const auto& a : wall_tool_paths) + { + for (const VariableWidthLines& b : a) + { + for (const ExtrusionLine& c : b) + { + const Polygon& poly = c.toPolygon(); + if (c.is_closed_) + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + } + } + + Shape supported_area = support_lines.offset(infill_line_width / 2); + + PointsSet requires_support; + + const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1; + if (skin_layer_nr < mesh.layers.size()) + { + for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) + { + for (const SkinPart& skin_part : part_i.skin_parts) + { + Shape outline = skin_part.outline; + Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. + infill_line_width, + std::numeric_limits::max() }; + outline = s.polygon(outline); + + // infill_lines.push_back(outline[0].toPseudoOpenPolyline()); + + const Shape& infill_area = part.getOwnInfillArea(); + + for (const PointsSet& poly : outline) + { + for (const Point2LL& point : poly) + { + if (infill_area.inside(point) && ! supported_area.inside(point)) + { + requires_support.push_back(point); + } + } + } + } + } + } + + // invert the supported_area by adding one huge polygon around the outside + if (! supported_area.empty()) + { + AABB worldbound{ supported_area }; + supported_area.push_back(worldbound.toPolygon()); + } + + // map each point into its area + std::map map; + for (const Point2LL& point : requires_support) + { + size_t idx = supported_area.findInside(point); + if (idx == NO_INDEX) + continue; + if (supported_area.empty()) + continue; + + map[idx].push_back(point); + } + + OpenLinesSet supporting_lines; + for (const auto& pair : map) + { + const Polygon& area = supported_area[pair.first]; + const PointsSet& points = pair.second; + + OpenLinesSet result_lines; + getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2), points, infill_line_width); + + supporting_lines.push_back(result_lines); + } + wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. const bool walls_generated = std::any_of( wall_tool_paths.cbegin(), @@ -2251,7 +2407,7 @@ bool FffGcodeWriter::processSingleLayerInfill( return vwl.empty(); })); }); - if (! infill_lines.empty() || ! infill_polygons.empty() || walls_generated) + if (! infill_lines.empty() || ! infill_polygons.empty() || walls_generated || ! supporting_lines.empty()) { added_something = true; gcode_layer.setIsInside(true); // going to print stuff inside print object @@ -2344,6 +2500,14 @@ bool FffGcodeWriter::processSingleLayerInfill( /*float_ratio = */ 1.0, near_start_location); } + gcode_layer.addLinesByOptimizer( + supporting_lines, + mesh_config.infill_config[0], + SpaceFillType::Lines, + enable_travel_optimization, + mesh.settings.get("infill_wipe_dist"), + /*float_ratio = */ 1.0, + near_start_location); } return added_something; } From e8f110a99e81d7ebd1b22ead75797615f12a4733 Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Fri, 19 Jul 2024 19:46:42 +0100 Subject: [PATCH 04/60] Improve ordering of printing of extra infill lines --- src/FffGcodeWriter.cpp | 344 +++++++++++++++++++++++++++-------------- 1 file changed, 231 insertions(+), 113 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index ad1160104b..5ebb92d3cc 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2041,6 +2041,229 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& } } +// Add a supporting line by cutting a few existing lines. +// 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. + auto findMatchingSegment = [&](Point2LL p) -> std::optional> + { + for (size_t i = 0; i < infill_lines.size(); ++i) + { + for (size_t j = 1; j < infill_lines[i].size(); ++j) + { + Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, infill_lines[i][j - 1], infill_lines[i][j]); + int64_t dist = vSize2(p - closest_here); + + if (dist < 25) // rounding + { + return std::make_tuple(i, j); + } + } + } + return std::nullopt; + }; + + auto front_match = findMatchingSegment(line_to_add.front()); + auto back_match = findMatchingSegment(line_to_add.back()); + + if (front_match && back_match) + { + auto [front_line_index, front_point_index] = *front_match; + auto [back_line_index, back_point_index] = *back_match; + + if (front_line_index == back_line_index) + { + /* 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. + */ + OpenPolyline& old_line = infill_lines[front_line_index]; + OpenPolyline new_line; + Point2LL x, y; + size_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; + } + else + { + y = line_to_add.front(); + x = line_to_add.back(); + y_index = front_point_index; + x_index = 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())); + } + 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 + */ + 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()); + old_front.setPoints(std::move(new_front.getPoints())); + old_back.setPoints(std::move(new_back.getPoints())); + } + } + else + { + // One or other end touches something other than infill + // we will just suffer a travel move in this case + infill_lines.push_back(line_to_add); + } +} + +/* Create a set of extra lines to support skins above. + * + * Skins above need their boundaries supported at a minimum. + * A straight line needs support just at the ends. + * A curve needs support at various points along the curve. + * We find these points by finding the minimal polygon which is + * always within line_width of the desired support. + * + * The strategy here is to figure out what is currently printed on + * this layer within the infill area by taking all currently printed + * lines and turning them into a giant hole-y shape. + * + * Then figure out extra infill_lines to add to support all points + * that are unsupported. The extra lines will always be straight + * and will always go between existing infill lines. + * + * Results get added to infill_lines. + */ +void addExtraLinesToSupportSurfacesAbove( + OpenLinesSet& infill_lines, + const Shape& infill_polygons, + const std::vector>& wall_tool_paths, + const SliceLayerPart& part, + coord_t infill_line_width, + const LayerPlan& gcode_layer, + const SliceMeshStorage& mesh) +{ + OpenLinesSet support_lines; + support_lines.push_back(infill_lines); + for (const auto& poly : part.getOwnInfillArea()) + { + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + + // We just want to grab all lines out of this datastructure + for (const auto& a : wall_tool_paths) + { + for (const VariableWidthLines& b : a) + { + for (const ExtrusionLine& c : b) + { + const Polygon& poly = c.toPolygon(); + if (c.is_closed_) + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + } + } + + Shape supported_area = support_lines.offset(infill_line_width / 2); + + PointsSet requires_support; + + const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1 + mesh.settings.get("skin_edge_support_layers"); + if (skin_layer_nr < mesh.layers.size()) + { + for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) + { + for (const SkinPart& skin_part : part_i.skin_parts) + { + Shape outline = skin_part.outline; + Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. + infill_line_width, + std::numeric_limits::max() }; + outline = s.polygon(outline); + + // Uncomment this line to add a debug line showing + // the supported points. + // for (auto poly: outline ) infill_lines.push_back(poly.toPseudoOpenPolyline()); + + const Shape& infill_area = part.getOwnInfillArea(); + + for (const PointsSet& poly : outline) + { + for (const Point2LL& point : poly) + { + if (infill_area.inside(point) && ! supported_area.inside(point)) + { + requires_support.push_back(point); + } + } + } + } + } + } + + // invert the supported_area by adding one huge polygon around the outside + if (! supported_area.empty()) + { + AABB worldbound{ supported_area }; + supported_area.push_back(worldbound.toPolygon()); + } + + // map each point into its area + std::map map; + for (const Point2LL& point : requires_support) + { + size_t idx = supported_area.findInside(point); + if (idx == NO_INDEX) + continue; + if (supported_area.empty()) + continue; + + map[idx].push_back(point); + } + + for (const auto& pair : map) + { + const Polygon& area = supported_area[pair.first]; + const PointsSet& points = pair.second; + + OpenLinesSet result_lines; + getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2), points, infill_line_width); + + for (const auto& line : part.getOwnInfillArea().intersection(result_lines)) + { + integrateSupportingLine(infill_lines, line); + } + } +} bool FffGcodeWriter::processSingleLayerInfill( const SliceDataStorage& storage, @@ -2286,112 +2509,15 @@ bool FffGcodeWriter::processSingleLayerInfill( infill_polygons.push_back(infill_polygons_here); } - /* Create a set of extra lines to support skins above. - * - * Skins above need their boundaries supported at a minimum. - * A straight line needs support just at the ends. - * A curve needs support at various points along the curve. - * We find these points by finding the minimal polygon which is - * always within line_width of the desired support. - * - * The strategy here is to figure out what is currently printed on - * this layer within the infill area by taking all currently printed - * lines and turning them into a giant hole-y shape. - * - * Then figure out extra lines to add to support all points - * that are unsupported. The extra lines will always be straight - * and will always go between existing infill lines. - */ - - OpenLinesSet support_lines; - support_lines.push_back(infill_lines); - for (const auto& poly : part.getOwnInfillArea()) - { - support_lines.push_back(poly.toPseudoOpenPolyline()); - } - - // We just want to grab all lines out of this datastructure - for (const auto& a : wall_tool_paths) - { - for (const VariableWidthLines& b : a) - { - for (const ExtrusionLine& c : b) - { - const Polygon& poly = c.toPolygon(); - if (c.is_closed_) - support_lines.push_back(poly.toPseudoOpenPolyline()); - } - } - } - - Shape supported_area = support_lines.offset(infill_line_width / 2); - - PointsSet requires_support; - - const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1; - if (skin_layer_nr < mesh.layers.size()) - { - for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) - { - for (const SkinPart& skin_part : part_i.skin_parts) - { - Shape outline = skin_part.outline; - Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. - infill_line_width, - std::numeric_limits::max() }; - outline = s.polygon(outline); - - // infill_lines.push_back(outline[0].toPseudoOpenPolyline()); - - const Shape& infill_area = part.getOwnInfillArea(); - - for (const PointsSet& poly : outline) - { - for (const Point2LL& point : poly) - { - if (infill_area.inside(point) && ! supported_area.inside(point)) - { - requires_support.push_back(point); - } - } - } - } - } - } - - // invert the supported_area by adding one huge polygon around the outside - if (! supported_area.empty()) - { - AABB worldbound{ supported_area }; - supported_area.push_back(worldbound.toPolygon()); - } - - // map each point into its area - std::map map; - for (const Point2LL& point : requires_support) - { - size_t idx = supported_area.findInside(point); - if (idx == NO_INDEX) - continue; - if (supported_area.empty()) - continue; - - map[idx].push_back(point); - } - - OpenLinesSet supporting_lines; - for (const auto& pair : map) - { - const Polygon& area = supported_area[pair.first]; - const PointsSet& points = pair.second; - - OpenLinesSet result_lines; - getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2), points, infill_line_width); + wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. - supporting_lines.push_back(result_lines); - } + if (mesh.settings.get("wall_line_count") // Disable feature if no walls - it can leave dangling lines at edges + && pattern != EFillMethod::LIGHTNING // Lightning doesn't make enclosed regions + && pattern != EFillMethod::CONCENTRIC // Doesn't handle 'holes' in infill lines very well + && pattern != EFillMethod::CROSS // Ditto + && pattern != EFillMethod::CROSS_3D) // Ditto + addExtraLinesToSupportSurfacesAbove(infill_lines, infill_polygons, wall_tool_paths, part, infill_line_width, gcode_layer, mesh); - wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. const bool walls_generated = std::any_of( wall_tool_paths.cbegin(), wall_tool_paths.cend(), @@ -2407,7 +2533,7 @@ bool FffGcodeWriter::processSingleLayerInfill( return vwl.empty(); })); }); - if (! infill_lines.empty() || ! infill_polygons.empty() || walls_generated || ! supporting_lines.empty()) + if (! infill_lines.empty() || ! infill_polygons.empty() || walls_generated) { added_something = true; gcode_layer.setIsInside(true); // going to print stuff inside print object @@ -2500,14 +2626,6 @@ bool FffGcodeWriter::processSingleLayerInfill( /*float_ratio = */ 1.0, near_start_location); } - gcode_layer.addLinesByOptimizer( - supporting_lines, - mesh_config.infill_config[0], - SpaceFillType::Lines, - enable_travel_optimization, - mesh.settings.get("infill_wipe_dist"), - /*float_ratio = */ 1.0, - near_start_location); } return added_something; } From 81f395cb690edfc89d13c3d8cbefbcdea956eb22 Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Sat, 20 Jul 2024 01:22:49 +0100 Subject: [PATCH 05/60] Add test for infill supporting the corners of skins --- include/ExtruderPlan.h | 1 + include/FffGcodeWriter.h | 2 + include/LayerPlan.h | 1 + tests/CMakeLists.txt | 1 + tests/FffGcodeWriterTest.cpp | 178 +++++ tests/test_default_settings.txt | 1243 +++++++++++++++++++++++++++++++ 6 files changed, 1426 insertions(+) create mode 100644 tests/FffGcodeWriterTest.cpp create mode 100644 tests/test_default_settings.txt diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 338dfba115..cc6687b5b5 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -42,6 +42,7 @@ class ExtruderPlan FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull); FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf); FRIEND_TEST(ExtruderPlanTest, BackPressureCompensationEmptyPlan); + friend class FffGcodeWriterTest_SurfaceGetsExtraInfillLinesUnderIt_Test; #endif public: size_t extruder_nr_{ 0 }; //!< The extruder used for this paths in the current plan. diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index f2337823f0..ac0a3ca274 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -38,6 +38,8 @@ struct MeshPathConfigs; class FffGcodeWriter : public NoCopy { friend class FffProcessor; // Because FffProcessor exposes finalize (TODO) + friend class FffGcodeWriterTest_SurfaceGetsExtraInfillLinesUnderIt_Test; + private: coord_t max_object_height; //!< The maximal height of all previously sliced meshgroups, used to avoid collision when moving to the next meshgroup to print. diff --git a/include/LayerPlan.h b/include/LayerPlan.h index d81bb9c437..48baf94ffb 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -53,6 +53,7 @@ class LayerPlan : public NoCopy friend class LayerPlanBuffer; #ifdef BUILD_TESTS friend class AddTravelTest; + friend class FffGcodeWriterTest_SurfaceGetsExtraInfillLinesUnderIt_Test; #endif public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e36893cee..f6d6eed09d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ include(GoogleTest) set(TESTS_SRC_BASE ClipperTest ExtruderPlanTest + FffGcodeWriterTest GCodeExportTest InfillTest LayerPlanTest diff --git a/tests/FffGcodeWriterTest.cpp b/tests/FffGcodeWriterTest.cpp new file mode 100644 index 0000000000..ce33ced698 --- /dev/null +++ b/tests/FffGcodeWriterTest.cpp @@ -0,0 +1,178 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "FffGcodeWriter.h" //Unit under test. + +#include +#include +#include +#include + + +#include +#include + +#include "geometry/OpenPolyline.h" +#include "geometry/Polygon.h" //To create example polygons. +#include "settings/Settings.h" //Settings to generate walls with. +#include "sliceDataStorage.h" //Sl +#include "LayerPlan.h" +#include "Slice.h" +#include "Application.h" +#include +#include + +// NOLINTBEGIN(*-magic-numbers) +namespace cura +{ +/*! + * Fixture that provides a basis for testing wall computation. + */ +class FffGcodeWriterTest : public testing::Test +{ +public: + Settings* settings; + FffGcodeWriter fff_gcode_writer; + + Shape square_shape; + // Square that fits wholly inside the above square + Shape inner_square; + + FffGcodeWriterTest() + : fff_gcode_writer() + { + square_shape.emplace_back(); + square_shape.back().emplace_back(0, 0); + square_shape.back().emplace_back(MM2INT(100), 0); + square_shape.back().emplace_back(MM2INT(100), MM2INT(100)); + square_shape.back().emplace_back(0, MM2INT(100)); + + inner_square.emplace_back(); + inner_square.back().emplace_back(MM2INT(10), MM2INT(20)); + inner_square.back().emplace_back(MM2INT(60), MM2INT(20)); + inner_square.back().emplace_back(MM2INT(60), MM2INT(60)); + inner_square.back().emplace_back(MM2INT(10), MM2INT(60)); + + } + + SliceDataStorage* setUpStorage() + { + constexpr size_t num_mesh_groups = 1; + Application::getInstance().current_slice_ = new Slice(num_mesh_groups); + + // Define all settings in the mesh group. The extruder train and model settings will fall back on that then. + settings = &Application::getInstance().current_slice_->scene.current_mesh_group->settings; + // Default settings. These are not (always) the FDM printer defaults, but sometimes just setting values that can be recognised + // uniquely as much as possible. + + + const auto path = std::filesystem::path(__FILE__).parent_path().append("test_default_settings.txt").string(); + std::ifstream file(path); + + std::string line; + while (std::getline(file, line)) { + size_t pos = line.find('='); + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + settings->add(key, value); + } + + Application::getInstance().current_slice_->scene.extruders.emplace_back(0, settings); // Add an extruder train. + + // Set the retraction settings (also copied by LayerPlan). + RetractionConfig retraction_config; + retraction_config.distance = settings->get("retraction_amount"); + retraction_config.prime_volume = settings->get("retraction_extra_prime_amount"); + retraction_config.speed = settings->get("retraction_retract_speed"); + retraction_config.primeSpeed = settings->get("retraction_prime_speed"); + retraction_config.zHop = settings->get("retraction_hop"); + retraction_config.retraction_min_travel_distance = settings->get("retraction_min_travel"); + retraction_config.retraction_extrusion_window = settings->get("retraction_extrusion_window"); + retraction_config.retraction_count_max = settings->get("retraction_count_max"); + + auto* result = new SliceDataStorage(); + result->retraction_wipe_config_per_extruder[0].retraction_config = retraction_config; + return result; + } +}; + +TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) +{ + // SETUP + + SliceDataStorage* storage = setUpStorage(); + + // Set the fan speed layer time settings (since the LayerPlan constructor copies these). + FanSpeedLayerTimeSettings fan_settings; + fan_settings.cool_min_layer_time = settings->get("cool_min_layer_time"); + fan_settings.cool_min_layer_time_fan_speed_max = settings->get("cool_min_layer_time_fan_speed_max"); + fan_settings.cool_fan_speed_0 = settings->get("cool_fan_speed_0"); + fan_settings.cool_fan_speed_min = settings->get("cool_fan_speed_min"); + fan_settings.cool_fan_speed_max = settings->get("cool_fan_speed_max"); + fan_settings.cool_min_speed = settings->get("cool_min_speed"); + fan_settings.cool_fan_full_layer = settings->get("cool_fan_full_layer"); + + Mesh mesh(*settings); + + LayerPlan gcode_layer(*storage, 100, 10000, 100, 0, {fan_settings}, 20, 10, 5000 ); + SliceMeshStorage mesh_storage(&mesh, 200); + size_t extruder_nr = 0; + MeshPathConfigs mesh_config(mesh_storage, 10, 100, {0.5}); + SliceLayerPart part; + + part.infill_area_per_combine_per_density = {{square_shape}}; + part.infill_area = square_shape; + + mesh_storage.layers[101].parts.emplace_back(); + SliceLayerPart& top_part = mesh_storage.layers[101].parts.back(); + top_part.skin_parts.emplace_back(); + top_part.skin_parts[0].outline.push_back(inner_square); + + // The actual thing we're testing + // We have set up a large square of infill on layer 100 + // And a smaller square of skin on layer 101 + // We're expecting the sparse infill on layer 100 to have + // some lines to support the corners of the layer above. + // But we arent wanting the whole area densely supported. + fff_gcode_writer.processSingleLayerInfill( + *storage, + gcode_layer, + mesh_storage, + extruder_nr, + mesh_config, + part + ); + + // Test helper + auto checkPointIsPassed = [&](Point2LL p, coord_t margin)-> bool { + 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); + int64_t dist = vSize2(p - closest_here); + + if (dist Date: Mon, 29 Jul 2024 02:23:03 +0100 Subject: [PATCH 06/60] Infill: Also support the middle lines of surfaces above --- src/FffGcodeWriter.cpp | 163 +++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 63 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 5ebb92d3cc..46d3868afb 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2145,20 +2145,41 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin } } +void wall_tool_paths2lines(const std::vector>& wall_tool_paths, OpenLinesSet& result) +{ + // We just want to grab all lines out of this datastructure + for (const auto& a : wall_tool_paths) + { + for (const VariableWidthLines& b : a) + { + for (const ExtrusionLine& c : b) + { + const Polygon& poly = c.toPolygon(); + if (c.is_closed_) + result.push_back(poly.toPseudoOpenPolyline()); + } + } + } +} + /* Create a set of extra lines to support skins above. * - * Skins above need their boundaries supported at a minimum. + * Skins above need to be held up. * A straight line needs support just at the ends. * A curve needs support at various points along the curve. - * We find these points by finding the minimal polygon which is - * always within line_width of the desired support. * - * The strategy here is to figure out what is currently printed on + * The strategy here is to figure out is currently printed on * this layer within the infill area by taking all currently printed * lines and turning them into a giant hole-y shape. * + * Then figure out what will be printed on the layer above + * (all extruded lines, walls, polygons, all combined). + * + * Then intersect these two things. For every 'hole', we 'simplify' + * the line through the hole, reducing curves to a few points. + * * Then figure out extra infill_lines to add to support all points - * that are unsupported. The extra lines will always be straight + * that lie within a hole. The extra lines will always be straight * and will always go between existing infill lines. * * Results get added to infill_lines. @@ -2172,87 +2193,103 @@ void addExtraLinesToSupportSurfacesAbove( const LayerPlan& gcode_layer, const SliceMeshStorage& mesh) { - OpenLinesSet support_lines; - support_lines.push_back(infill_lines); - for (const auto& poly : part.getOwnInfillArea()) - { - support_lines.push_back(poly.toPseudoOpenPolyline()); - } + // Where needs support? - // We just want to grab all lines out of this datastructure - for (const auto& a : wall_tool_paths) + const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1 + mesh.settings.get("skin_edge_support_layers"); + if (skin_layer_nr >= mesh.layers.size()) + return; + + OpenLinesSet printed_lines_on_layer_above; + for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) { - for (const VariableWidthLines& b : a) + for (const SkinPart& skin_part : part_i.skin_parts) { - for (const ExtrusionLine& c : b) + OpenLinesSet skin_lines; + Shape skin_polygons; + std::vector skin_paths; + + AngleDegrees skin_angle = 45; + if (mesh.skin_angles.size() > 0) { - const Polygon& poly = c.toPolygon(); - if (c.is_closed_) - support_lines.push_back(poly.toPseudoOpenPolyline()); + skin_angle = mesh.skin_angles.at(skin_layer_nr % mesh.skin_angles.size()); } + + // Approximation of the skin. + Infill infill_comp( + mesh.settings.get("top_bottom_pattern"), + false, + false, + skin_part.outline, + infill_line_width, + infill_line_width, + 0, + 1, + skin_angle, + 0, + 0, + 0, + 0, + mesh.settings.get("skin_outline_count"), + 0, + {}, + false); + infill_comp.generate(skin_paths, skin_polygons, skin_lines, mesh.settings, 0, SectionType::SKIN); + + wall_tool_paths2lines({ skin_paths }, printed_lines_on_layer_above); + for (const Polygon& poly : skin_polygons) + printed_lines_on_layer_above.push_back(poly.toPseudoOpenPolyline()); + printed_lines_on_layer_above.push_back(skin_lines); } } - Shape supported_area = support_lines.offset(infill_line_width / 2); - - PointsSet requires_support; + if (printed_lines_on_layer_above.empty()) + return; - const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1 + mesh.settings.get("skin_edge_support_layers"); - if (skin_layer_nr < mesh.layers.size()) + // What shape is the supporting infill? + OpenLinesSet support_lines; + support_lines.push_back(infill_lines); + for (const auto& poly : part.getOwnInfillArea()) { - for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) - { - for (const SkinPart& skin_part : part_i.skin_parts) - { - Shape outline = skin_part.outline; - Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. - infill_line_width, - std::numeric_limits::max() }; - outline = s.polygon(outline); + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + wall_tool_paths2lines(wall_tool_paths, support_lines); - // Uncomment this line to add a debug line showing - // the supported points. - // for (auto poly: outline ) infill_lines.push_back(poly.toPseudoOpenPolyline()); + Shape supported_area = support_lines.offset(infill_line_width / 2); + if (supported_area.empty()) + return; - const Shape& infill_area = part.getOwnInfillArea(); + // invert the supported_area by adding one huge polygon around the outside + supported_area.push_back(AABB{ supported_area }.toPolygon()); - for (const PointsSet& poly : outline) - { - for (const Point2LL& point : poly) - { - if (infill_area.inside(point) && ! supported_area.inside(point)) - { - requires_support.push_back(point); - } - } - } - } - } - } + const Shape& inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); - // invert the supported_area by adding one huge polygon around the outside - if (! supported_area.empty()) - { - AABB worldbound{ supported_area }; - supported_area.push_back(worldbound.toPolygon()); - } + 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(-10); + + Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. + infill_line_width, + std::numeric_limits::max() }; // map each point into its area std::map map; - for (const Point2LL& point : requires_support) + + for (const OpenPolyline& a : unsupported_line_segments) { - size_t idx = supported_area.findInside(point); - if (idx == NO_INDEX) - continue; - if (supported_area.empty()) - continue; + const OpenPolyline& simplified = s.polyline(a); + for (const Point2LL& point : simplified) + { + size_t idx = expanded_inv_supported_area.findInside(point); + if (idx == NO_INDEX) + continue; - map[idx].push_back(point); + map[idx].push_back(point); + } } for (const auto& pair : map) { - const Polygon& area = supported_area[pair.first]; + const Polygon& area = expanded_inv_supported_area[pair.first]; const PointsSet& points = pair.second; OpenLinesSet result_lines; From 8d0e2aec1a276d8bf57472685c4d7ddccaec7fbc Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Mon, 29 Jul 2024 16:55:19 +0100 Subject: [PATCH 07/60] Add ability for infill to support lines within skins, and add config option and tests for the same --- include/settings/EnumSettings.h | 13 +++++ src/FffGcodeWriter.cpp | 37 +++++++++++-- src/settings/Settings.cpp | 18 +++++++ tests/FffGcodeWriterTest.cpp | 96 +++++++++++++++++++++------------ tests/test_default_settings.txt | 1 + 5 files changed, 127 insertions(+), 38 deletions(-) diff --git a/include/settings/EnumSettings.h b/include/settings/EnumSettings.h index 650f6a06dc..aa16eab795 100644 --- a/include/settings/EnumSettings.h +++ b/include/settings/EnumSettings.h @@ -31,6 +31,19 @@ enum class EFillMethod PLUGIN, // Place plugin after none to prevent it from being tested in the gtest suite. }; + +/*! + * Enum for the value of extra_infill_lines_to_support_skins + * This enum defines what extra lines should be added to infill to support + * skins above. + */ +enum class EExtraInfillLinesToSupportSkins +{ + WALLS_AND_LINES, + WALLS, + NONE, +}; + /*! * Type of platform adhesion. */ diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 46d3868afb..7476334d49 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2195,6 +2195,10 @@ void addExtraLinesToSupportSurfacesAbove( { // Where needs support? + const auto enabled = mesh.settings.get("extra_infill_lines_to_support_skins"); + if (enabled == EExtraInfillLinesToSupportSkins::NONE) + return; + const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1 + mesh.settings.get("skin_edge_support_layers"); if (skin_layer_nr >= mesh.layers.size()) return; @@ -2236,9 +2240,31 @@ void addExtraLinesToSupportSurfacesAbove( infill_comp.generate(skin_paths, skin_polygons, skin_lines, mesh.settings, 0, SectionType::SKIN); wall_tool_paths2lines({ skin_paths }, printed_lines_on_layer_above); - for (const Polygon& poly : skin_polygons) - printed_lines_on_layer_above.push_back(poly.toPseudoOpenPolyline()); - printed_lines_on_layer_above.push_back(skin_lines); + if (enabled == EExtraInfillLinesToSupportSkins::WALLS_AND_LINES) + { + for (const Polygon& poly : skin_polygons) + printed_lines_on_layer_above.push_back(poly.toPseudoOpenPolyline()); + printed_lines_on_layer_above.push_back(skin_lines); + } + } + } + + /* move all points "inwards" by line_width to ensure a good overlap. + * Eg. Old Point New Point + * | | + * | X| + * -------X --------- + */ + for (OpenPolyline& poly : printed_lines_on_layer_above) + { + OpenPolyline copy = poly; + auto orig_it = poly.begin(); + for (auto it = copy.begin(); it != copy.end(); ++it, ++orig_it) + { + if (it > copy.begin()) + *orig_it += normal(*(it) - *(it - 1), infill_line_width / 2); + if (it < copy.end() - 1) + *orig_it += normal(*(it + 1) - *(it), infill_line_width / 2); } } @@ -2248,12 +2274,15 @@ void addExtraLinesToSupportSurfacesAbove( // What shape is the supporting infill? OpenLinesSet support_lines; support_lines.push_back(infill_lines); + // The edge of the infill area is also considered supported for (const auto& poly : part.getOwnInfillArea()) { support_lines.push_back(poly.toPseudoOpenPolyline()); } + // Infill walls can support the layer above wall_tool_paths2lines(wall_tool_paths, support_lines); + // Turn the lines into a giant shape. Shape supported_area = support_lines.offset(infill_line_width / 2); if (supported_area.empty()) return; @@ -2293,7 +2322,7 @@ void addExtraLinesToSupportSurfacesAbove( const PointsSet& points = pair.second; OpenLinesSet result_lines; - getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2), points, infill_line_width); + getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2 + 10), points, infill_line_width); for (const auto& line : part.getOwnInfillArea().intersection(result_lines)) { diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index cd6d2719f4..4e39ef8555 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -458,6 +458,24 @@ EPlatformAdhesion Settings::get(const std::string& key) const } } +template<> +EExtraInfillLinesToSupportSkins Settings::get(const std::string& key) const +{ + const std::string& value = get(key); + using namespace cura::utils; + switch (hash_enum(value)) + { + case "walls_and_lines"_sw: + return EExtraInfillLinesToSupportSkins::WALLS_AND_LINES; + case "walls"_sw: + return EExtraInfillLinesToSupportSkins::WALLS; + case "none"_sw: + return EExtraInfillLinesToSupportSkins::NONE; + default: + return EExtraInfillLinesToSupportSkins::WALLS_AND_LINES; + } +} + template<> ESupportType Settings::get(const std::string& key) const { diff --git a/tests/FffGcodeWriterTest.cpp b/tests/FffGcodeWriterTest.cpp index ce33ced698..87b20cb64e 100644 --- a/tests/FffGcodeWriterTest.cpp +++ b/tests/FffGcodeWriterTest.cpp @@ -3,24 +3,25 @@ #include "FffGcodeWriter.h" //Unit under test. -#include -#include -#include #include - +#include +#include +#include #include #include +#include +#include + +#include "Application.h" +#include "LayerPlan.h" +#include "Slice.h" +#include "arcus/MockCommunication.h" // To prevent calls to any missing Communication class. #include "geometry/OpenPolyline.h" #include "geometry/Polygon.h" //To create example polygons. #include "settings/Settings.h" //Settings to generate walls with. #include "sliceDataStorage.h" //Sl -#include "LayerPlan.h" -#include "Slice.h" -#include "Application.h" -#include -#include // NOLINTBEGIN(*-magic-numbers) namespace cura @@ -34,18 +35,18 @@ class FffGcodeWriterTest : public testing::Test Settings* settings; FffGcodeWriter fff_gcode_writer; - Shape square_shape; + Shape outer_square; // Square that fits wholly inside the above square Shape inner_square; FffGcodeWriterTest() : fff_gcode_writer() { - square_shape.emplace_back(); - square_shape.back().emplace_back(0, 0); - square_shape.back().emplace_back(MM2INT(100), 0); - square_shape.back().emplace_back(MM2INT(100), MM2INT(100)); - square_shape.back().emplace_back(0, MM2INT(100)); + outer_square.emplace_back(); + outer_square.back().emplace_back(0, 0); + outer_square.back().emplace_back(MM2INT(100), 0); + outer_square.back().emplace_back(MM2INT(100), MM2INT(100)); + outer_square.back().emplace_back(0, MM2INT(100)); inner_square.emplace_back(); inner_square.back().emplace_back(MM2INT(10), MM2INT(20)); @@ -53,18 +54,15 @@ class FffGcodeWriterTest : public testing::Test inner_square.back().emplace_back(MM2INT(60), MM2INT(60)); inner_square.back().emplace_back(MM2INT(10), MM2INT(60)); + Application::getInstance().communication_ = new MockCommunication(); } SliceDataStorage* setUpStorage() { - constexpr size_t num_mesh_groups = 1; - Application::getInstance().current_slice_ = new Slice(num_mesh_groups); + Application::getInstance().current_slice_ = new Slice(1); // Define all settings in the mesh group. The extruder train and model settings will fall back on that then. - settings = &Application::getInstance().current_slice_->scene.current_mesh_group->settings; - // Default settings. These are not (always) the FDM printer defaults, but sometimes just setting values that can be recognised - // uniquely as much as possible. - + settings = &Application::getInstance().current_slice_->scene.settings; const auto path = std::filesystem::path(__FILE__).parent_path().append("test_default_settings.txt").string(); std::ifstream file(path); @@ -75,7 +73,9 @@ class FffGcodeWriterTest : public testing::Test std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); settings->add(key, value); - } + } + + settings->add("infill_line_distance", "10"); Application::getInstance().current_slice_->scene.extruders.emplace_back(0, settings); // Add an extruder train. @@ -99,7 +99,6 @@ class FffGcodeWriterTest : public testing::Test TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) { // SETUP - SliceDataStorage* storage = setUpStorage(); // Set the fan speed layer time settings (since the LayerPlan constructor copies these). @@ -120,8 +119,8 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) MeshPathConfigs mesh_config(mesh_storage, 10, 100, {0.5}); SliceLayerPart part; - part.infill_area_per_combine_per_density = {{square_shape}}; - part.infill_area = square_shape; + part.infill_area_per_combine_per_density = { { outer_square } }; + part.infill_area = outer_square; mesh_storage.layers[101].parts.emplace_back(); SliceLayerPart& top_part = mesh_storage.layers[101].parts.back(); @@ -143,6 +142,14 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) part ); + /* Useful code if you're debugging this test. Also add this test as a friend in GCodeExport.h + GCodeExport gcode_export; + std::ofstream output_file; + output_file.open("test_result.gcode"); + gcode_export.output_stream_ = &output_file; + gcode_layer.writeGCode(gcode_export); + */ + // Test helper auto checkPointIsPassed = [&](Point2LL p, coord_t margin)-> bool { Point2LL last; @@ -162,16 +169,37 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) // Check the results for (auto poly:inner_square) for (auto point:poly) - EXPECT_TRUE(checkPointIsPassed(point, MM2INT(1))) << "The corners of this square need an infill line under them so they dont droop down!"; - + EXPECT_TRUE(checkPointIsPassed(point, MM2INT(0.3))) << "The corners of this square need an infill line under them so they dont droop down!"; + int ctr = 0; - if (checkPointIsPassed({MM2INT(30),MM2INT(30)}, MM2INT(1))) ctr++; - if (checkPointIsPassed({MM2INT(8),MM2INT(64)}, MM2INT(1))) ctr++; - if (checkPointIsPassed({MM2INT(16),MM2INT(42)}, MM2INT(1))) ctr++; - if (checkPointIsPassed({MM2INT(77),MM2INT(33)}, MM2INT(1))) ctr++; - if (checkPointIsPassed({MM2INT(12),MM2INT(1)}, MM2INT(1))) ctr++; - if (checkPointIsPassed({MM2INT(30),MM2INT(30)}, MM2INT(1))) ctr++; - EXPECT_LE(ctr, 3) << "Randomly selected points should not be supported by sparse infill"; + if (checkPointIsPassed({ MM2INT(90), MM2INT(30) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(8), MM2INT(64) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(5), MM2INT(72) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(77), MM2INT(33) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(12), MM2INT(1) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(88), MM2INT(70) }, MM2INT(0.3))) + ctr++; + EXPECT_LE(ctr, 3) << "Selected points outside the square should not be supported by sparse infill"; + + ctr = 0; + if (checkPointIsPassed({ MM2INT(30), MM2INT(32) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(40), MM2INT(35) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(35), MM2INT(49) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(21), MM2INT(42) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(48), MM2INT(32) }, MM2INT(0.3))) + ctr++; + if (checkPointIsPassed({ MM2INT(29), MM2INT(45) }, MM2INT(0.3))) + ctr++; + EXPECT_LE(ctr, 3) << "Selected points in the middle of the square should not be supported by sparse infill"; } } // namespace cura diff --git a/tests/test_default_settings.txt b/tests/test_default_settings.txt index 6f6eebd517..34b5d53147 100644 --- a/tests/test_default_settings.txt +++ b/tests/test_default_settings.txt @@ -750,6 +750,7 @@ machine_nozzle_heat_up_speed=2.0 jerk_wall_0=20 retraction_extra_prime_amount=0 skin_edge_support_thickness=0 +extra_infill_lines_to_support_skins=walls_and_lines speed_topbottom=40.0 jerk_prime_tower=20 retraction_hop=1 From 99e57a83815225031cd0fb1158fdc8f15a323aa2 Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Tue, 30 Jul 2024 12:39:41 +0100 Subject: [PATCH 08/60] Bugfix: Extra infill support lines could be mispositioned by up to 1 line_width --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 7476334d49..973fc3e844 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2262,7 +2262,7 @@ void addExtraLinesToSupportSurfacesAbove( for (auto it = copy.begin(); it != copy.end(); ++it, ++orig_it) { if (it > copy.begin()) - *orig_it += normal(*(it) - *(it - 1), infill_line_width / 2); + *orig_it += normal(*(it - 1) - *(it), infill_line_width / 2); if (it < copy.end() - 1) *orig_it += normal(*(it + 1) - *(it), infill_line_width / 2); } From 4fb9e8109a9a9bcaba8a5e5b3b893327d10ca7d5 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 1 Aug 2024 09:10:31 +0200 Subject: [PATCH 09/60] Add missing includes Needed for `std::find_if`, `std::find` and `std::reverse` Alos removed a couple of redundant `else` statement after a `return` statement --- include/utils/ExtrusionLine.h | 2 ++ src/geometry/PartsView.cpp | 3 +++ src/geometry/Polyline.cpp | 11 +++-------- src/infill/ZigzagConnectorProcessor.cpp | 21 ++++++++++----------- src/utils/AABB.cpp | 1 + 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/utils/ExtrusionLine.h b/include/utils/ExtrusionLine.h index 987ce1b783..066a52e4f5 100644 --- a/include/utils/ExtrusionLine.h +++ b/include/utils/ExtrusionLine.h @@ -13,6 +13,8 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" +#include + namespace cura { diff --git a/src/geometry/PartsView.cpp b/src/geometry/PartsView.cpp index 5e080b2aea..a1d8fc1960 100644 --- a/src/geometry/PartsView.cpp +++ b/src/geometry/PartsView.cpp @@ -6,6 +6,9 @@ #include "geometry/Polygon.h" #include "geometry/SingleShape.h" +#include +#include + namespace cura { diff --git a/src/geometry/Polyline.cpp b/src/geometry/Polyline.cpp index d64341eada..c476f8e461 100644 --- a/src/geometry/Polyline.cpp +++ b/src/geometry/Polyline.cpp @@ -3,6 +3,7 @@ #include "geometry/Polyline.h" +#include #include #include @@ -104,10 +105,7 @@ Polyline::const_segments_iterator Polyline::endSegments() const { return const_segments_iterator(end(), begin(), end()); } - else - { - return const_segments_iterator(size() > 1 ? std::prev(end()) : end(), begin(), end()); - } + return const_segments_iterator(size() > 1 ? std::prev(end()) : end(), begin(), end()); } Polyline::segments_iterator Polyline::beginSegments() @@ -121,10 +119,7 @@ Polyline::segments_iterator Polyline::endSegments() { return segments_iterator(end(), begin(), end()); } - else - { - return segments_iterator(size() > 1 ? std::prev(end()) : end(), begin(), end()); - } + return segments_iterator(size() > 1 ? std::prev(end()) : end(), begin(), end()); } coord_t Polyline::length() const diff --git a/src/infill/ZigzagConnectorProcessor.cpp b/src/infill/ZigzagConnectorProcessor.cpp index 5cc94e9845..2ea14d3953 100644 --- a/src/infill/ZigzagConnectorProcessor.cpp +++ b/src/infill/ZigzagConnectorProcessor.cpp @@ -10,6 +10,8 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" +#include + using namespace cura; @@ -93,17 +95,14 @@ bool ZigzagConnectorProcessor::handleConnectorTooCloseToSegment(const coord_t sc { return false; } - else - { - return std::find_if( - current_connector_.begin(), - current_connector_.end(), - [scanline_x, min_distance_to_scanline](const Point2LL& point) - { - return std::abs(point.X - scanline_x) >= min_distance_to_scanline; - }) - == current_connector_.end(); - } + return std::find_if( + current_connector_.begin(), + current_connector_.end(), + [scanline_x, min_distance_to_scanline](const Point2LL& point) + { + return std::abs(point.X - scanline_x) >= min_distance_to_scanline; + }) + == current_connector_.end(); } void ZigzagConnectorProcessor::registerScanlineSegmentIntersection(const Point2LL& intersection, int scanline_index, coord_t min_distance_to_scanline) diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 6707775c3f..163c6a5f26 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -3,6 +3,7 @@ #include "utils/AABB.h" +#include #include #include "geometry/Polygon.h" From f8a075bb1d6b1f0b1f0e8c2444e23d7076228507 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 1 Aug 2024 09:18:33 +0200 Subject: [PATCH 10/60] Bump up rapidjson version to latest in CCI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RapidJson 1.1.0 has a bug in `document.h` which is currently an issue on a fresh install. Probably due to changing of the order of includes in CuraEngine. ``` error: assignment of read-only member ‘rapidjson::GenericStringRef::length’ ``` --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 8672d9a94b..d40e9c70c4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -125,7 +125,7 @@ def requirements(self): self.requires("protobuf/3.21.12") self.requires("clipper/6.4.2@ultimaker/stable") self.requires("boost/1.82.0") - self.requires("rapidjson/1.1.0") + self.requires("rapidjson/cci.20230929") self.requires("stb/20200203") self.requires("spdlog/1.12.0") self.requires("fmt/10.1.1") From 499136d335cef6bdde9b0ffb8f2c3c7571a81403 Mon Sep 17 00:00:00 2001 From: jellespijker Date: Thu, 1 Aug 2024 07:27:06 +0000 Subject: [PATCH 11/60] Applied clang-format. --- include/utils/ExtrusionLine.h | 4 ++-- src/geometry/PartsView.cpp | 6 +++--- src/infill/ZigzagConnectorProcessor.cpp | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/utils/ExtrusionLine.h b/include/utils/ExtrusionLine.h index 066a52e4f5..fa76eaca93 100644 --- a/include/utils/ExtrusionLine.h +++ b/include/utils/ExtrusionLine.h @@ -5,6 +5,8 @@ #ifndef UTILS_EXTRUSION_LINE_H #define UTILS_EXTRUSION_LINE_H +#include + #include #include #include @@ -13,8 +15,6 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" -#include - namespace cura { diff --git a/src/geometry/PartsView.cpp b/src/geometry/PartsView.cpp index a1d8fc1960..c1370895c5 100644 --- a/src/geometry/PartsView.cpp +++ b/src/geometry/PartsView.cpp @@ -3,12 +3,12 @@ #include "geometry/PartsView.h" -#include "geometry/Polygon.h" -#include "geometry/SingleShape.h" - #include #include +#include "geometry/Polygon.h" +#include "geometry/SingleShape.h" + namespace cura { diff --git a/src/infill/ZigzagConnectorProcessor.cpp b/src/infill/ZigzagConnectorProcessor.cpp index 2ea14d3953..7aadebc911 100644 --- a/src/infill/ZigzagConnectorProcessor.cpp +++ b/src/infill/ZigzagConnectorProcessor.cpp @@ -3,6 +3,7 @@ #include "infill/ZigzagConnectorProcessor.h" +#include #include #include "geometry/OpenPolyline.h" @@ -10,8 +11,6 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" -#include - using namespace cura; From 5ea8d8f0d98128e345e3c7edcdfdd047d8306d69 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 1 Aug 2024 10:12:50 +0200 Subject: [PATCH 12/60] Use smart pointer for communication Contribute to NP-327 --- include/Application.h | 3 ++- src/Application.cpp | 8 +++----- src/LayerPlan.cpp | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/Application.h b/include/Application.h index 205766f62f..6884c56eae 100644 --- a/include/Application.h +++ b/include/Application.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "utils/NoCopy.h" @@ -38,7 +39,7 @@ class Application : NoCopy * can assume that it is safe to access this without checking whether it is * initialised. */ - Communication* communication_ = nullptr; + std::unique_ptr communication_ { nullptr }; /* * \brief The slice that is currently ongoing. diff --git a/src/Application.cpp b/src/Application.cpp index a2a271f617..4ca12092e9 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -45,7 +45,6 @@ Application::Application() Application::~Application() { - delete communication_; delete thread_pool_; } @@ -100,9 +99,9 @@ void Application::connect() } } - ArcusCommunication* arcus_communication = new ArcusCommunication(); + auto arcus_communication = std::make_unique(); arcus_communication->connect(ip, port); - communication_ = arcus_communication; + communication_ = std::move(arcus_communication); } #endif // ARCUS @@ -214,8 +213,7 @@ void Application::slice() { arguments.emplace_back(argv_[argument_index]); } - - communication_ = new CommandLine(arguments); + communication_ = std::make_unique(arguments); } void Application::run(const size_t argc, char** argv) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1ef4b35bda..b39043b8ff 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1983,7 +1983,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) void LayerPlan::writeGCode(GCodeExport& gcode) { - Communication* communication = Application::getInstance().communication_; + auto& communication = Application::getInstance().communication_; communication->setLayerForSend(layer_nr_); communication->sendCurrentPosition(gcode.getPositionXY()); gcode.setLayerNr(layer_nr_); @@ -2536,7 +2536,7 @@ bool LayerPlan::writePathWithCoasting( Point2LL prev_pt = gcode.getPositionXY(); { // write normal extrude path: - Communication* communication = Application::getInstance().communication_; + auto& 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); From 1d066a4e32e8dfa87051e24b9c996c90bc744aca Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 1 Aug 2024 11:16:25 +0200 Subject: [PATCH 13/60] Introduce EmscriptenCommunication class Contribute to NP-327 --- CMakeLists.txt | 1 + .../communication/EmscriptenCommunication.h | 32 ++++++++++++++++++ src/Application.cpp | 7 +++- src/communication/EmscriptenCommunication.cpp | 33 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 include/communication/EmscriptenCommunication.h create mode 100644 src/communication/EmscriptenCommunication.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2287e8f92d..468d369e45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ set(engine_SRCS # Except main.cpp. src/communication/ArcusCommunication.cpp src/communication/ArcusCommunicationPrivate.cpp src/communication/CommandLine.cpp + src/communication/EmscriptenCommunication.cpp src/communication/Listener.cpp src/infill/ImageBasedDensityProvider.cpp diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h new file mode 100644 index 0000000000..d292b40e9f --- /dev/null +++ b/include/communication/EmscriptenCommunication.h @@ -0,0 +1,32 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef EMSCRIPTENCOMMUNICATION_H +#define EMSCRIPTENCOMMUNICATION_H +// #ifdef __EMSCRIPTEN__ + +#include "communication/CommandLine.h" + +namespace cura +{ +class Settings; + +class EmscriptenCommunication : public CommandLine +{ +public: + EmscriptenCommunication(const std::vector& arguments) + : CommandLine(arguments){}; + + void sendSliceUUID(const std::string& slice_uuid) const override; + + void sendPrintTimeMaterialEstimates() const override; + + void sendProgress(double progress) const override; + + void sliceNext() override; + +}; + +} // namespace cura +// #endif // __EMSCRIPTEN__ +#endif // EMSCRIPTENCOMMUNICATION_H diff --git a/src/Application.cpp b/src/Application.cpp index 4ca12092e9..9658a463ce 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -20,6 +20,7 @@ #include "communication/ArcusCommunication.h" //To connect via Arcus to the front-end. #include "communication/CommandLine.h" //To use the command line to slice stuff. +#include "communication/EmscriptenCommunication.h" // To use Emscripten to slice stuff. #include "progress/Progress.h" #include "utils/ThreadPool.h" #include "utils/string.h" //For stringcasecompare. @@ -213,7 +214,11 @@ void Application::slice() { arguments.emplace_back(argv_[argument_index]); } - communication_ = std::make_unique(arguments); + // #ifdef __EMSCRIPTEN__ + communication_ = std::make_unique(arguments); + // #else + // communication_ = std::make_unique(arguments); + // #endif } void Application::run(const size_t argc, char** argv) diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp new file mode 100644 index 0000000000..7bf29f0094 --- /dev/null +++ b/src/communication/EmscriptenCommunication.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "communication/EmscriptenCommunication.h" + + +#include "spdlog/spdlog.h" + +namespace cura +{ + +void EmscriptenCommunication::sendSliceUUID(const std::string& slice_uuid) const +{ + spdlog::info("Slice UUID: {}", slice_uuid); +} + +void EmscriptenCommunication::sendPrintTimeMaterialEstimates() const +{ + spdlog::info("Print time and material estimates"); +} + +void EmscriptenCommunication::sendProgress(double progress) const +{ + spdlog::info("Progress: {}", progress); +} + +void EmscriptenCommunication::sliceNext() +{ + spdlog::info("Slice next"); + CommandLine::sliceNext(); +}; + +} // namespace cura From 0533f8ca0623b5128ee96173ccc2f3fdd71dbff7 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Fri, 2 Aug 2024 16:06:14 +0200 Subject: [PATCH 14/60] Enable Emscripten support in communication module Refactored conditional compilation to enable Emscripten-specific behavior in the communication module. Updated CMakeLists.txt to handle Emscripten build settings and refactored EmscriptenCommunication to properly manage progress handling and command-line arguments. Contribute to NP-327 --- CMakeLists.txt | 23 ++++++++++++++++++- include/communication/CommandLine.h | 17 +++++++------- .../communication/EmscriptenCommunication.h | 10 ++++---- src/Application.cpp | 8 +++---- src/communication/CommandLine.cpp | 14 ----------- src/communication/EmscriptenCommunication.cpp | 21 +++++++++++++++-- 6 files changed, 59 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 468d369e45..1bd6d1a6c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,7 +271,28 @@ endif () if (CMAKE_CXX_PLATFORM_ID STREQUAL "emscripten") message(STATUS "Building for Emscripten") - target_link_options(_CuraEngine PUBLIC -Wno-unused-command-line-argument -sINVOKE_RUN=0 -sEXPORT_NAME=CuraEngine -sEXPORTED_RUNTIME_METHODS=[callMain,FS] -sFORCE_FILESYSTEM=1 -sALLOW_MEMORY_GROWTH=1 -sEXPORT_ES6=1 -sMODULARIZE=1 -sSINGLE_FILE=1 -sENVIRONMENT=worker -sERROR_ON_UNDEFINED_SYMBOLS=0 -lembind --embind-emit-tsd CuraEngine.d.ts) + target_link_options(_CuraEngine + PUBLIC + "SHELL:-sINVOKE_RUN=0" + "SHELL:-sEXPORT_NAME=CuraEngine" + "SHELL:-sEXPORTED_RUNTIME_METHODS=[callMain,FS]" + "SHELL:-sFORCE_FILESYSTEM=1" + "SHELL:-sALLOW_MEMORY_GROWTH=1" + "SHELL:-sEXPORT_ES6=1" + "SHELL:-sMODULARIZE=1" + "SHELL:-sSINGLE_FILE=1" + "SHELL:-sENVIRONMENT=web" + "SHELL:-sERROR_ON_UNDEFINED_SYMBOLS=0" + "SHELL:-sWASM_BIGINT=1" + "SHELL:-sSTACK_SIZE=196608" + $<$:SHELL:-sASSERTIONS=2> + $<$:SHELL:-sSAFE_HEAP=1> + $<$:SHELL:-sSTACK_OVERFLOW_CHECK=2> + $<$:SHELL:-g3> + $<$:SHELL:-gsource-map> + "SHELL:-lembind" + "SHELL:--embind-emit-tsd CuraEngine.d.ts" + ) endif () target_link_libraries(CuraEngine PRIVATE diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 55c742871a..59d908d79e 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -27,6 +27,8 @@ using container_setting_map = std::unordered_map; class CommandLine : public Communication { public: + CommandLine() = default; + /* * \brief Construct a new communicator that interprets the command line to * start a slice. @@ -155,18 +157,15 @@ class CommandLine : public Communication */ void sliceNext() override; -private: -#ifdef __EMSCRIPTEN__ - std::string progressHandler; -#endif +protected: + /* + * \brief The command line arguments that the application was called with. + */ + std::vector arguments_; +private: std::vector search_directories_; - /* - * \brief The command line arguments that the application was called with. - */ - std::vector arguments_; - /* * The last progress update that we output to stdcerr. */ diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index d292b40e9f..d8590db21e 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -3,7 +3,7 @@ #ifndef EMSCRIPTENCOMMUNICATION_H #define EMSCRIPTENCOMMUNICATION_H -// #ifdef __EMSCRIPTEN__ +#ifdef __EMSCRIPTEN__ #include "communication/CommandLine.h" @@ -13,9 +13,10 @@ class Settings; class EmscriptenCommunication : public CommandLine { + std::string progressHandler; + public: - EmscriptenCommunication(const std::vector& arguments) - : CommandLine(arguments){}; + EmscriptenCommunication(const std::vector& arguments); void sendSliceUUID(const std::string& slice_uuid) const override; @@ -28,5 +29,6 @@ class EmscriptenCommunication : public CommandLine }; } // namespace cura -// #endif // __EMSCRIPTEN__ + +#endif // __EMSCRIPTEN__ #endif // EMSCRIPTENCOMMUNICATION_H diff --git a/src/Application.cpp b/src/Application.cpp index 9658a463ce..f167825b11 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -214,11 +214,11 @@ void Application::slice() { arguments.emplace_back(argv_[argument_index]); } - // #ifdef __EMSCRIPTEN__ +#ifdef __EMSCRIPTEN__ communication_ = std::make_unique(arguments); - // #else - // communication_ = std::make_unique(arguments); - // #endif +#else + communication_ = std::make_unique(arguments); +#endif } void Application::run(const size_t argc, char** argv) diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 12e04adc13..44c02c33cb 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -32,10 +32,6 @@ #include "utils/format/filesystem_path.h" #include "utils/views/split_paths.h" -#ifdef __EMSCRIPTEN__ -#include -#endif - namespace cura { @@ -124,13 +120,6 @@ void CommandLine::sendProgress(double progress) const { return; } - // TODO: Do we want to print a progress bar? We'd need a better solution to not have that progress bar be ruined by any logging. -#ifdef __EMSCRIPTEN__ - // Call progress handler with progress - char js[100]; - std::sprintf(js, "globalThis[\"%s\"](%f)", progressHandler.c_str(), progress); - emscripten_run_script(js); -#endif } void CommandLine::sliceNext() @@ -203,15 +192,12 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } -#ifdef __EMSCRIPTEN__ else if (argument.find("--progress") == 0) { // Store progress handler name argument_index++; argument = arguments_[argument_index]; - progressHandler = argument; } -#endif else { spdlog::error("Unknown option: {}", argument); diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 7bf29f0094..6a3084e0e2 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -1,14 +1,29 @@ // Copyright (c) 2024 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#ifdef __EMSCRIPTEN__ #include "communication/EmscriptenCommunication.h" +#include -#include "spdlog/spdlog.h" +#include +#include +#include +#include namespace cura { +EmscriptenCommunication::EmscriptenCommunication(const std::vector& arguments) + : CommandLine(arguments) +{ + spdlog::info("Emscripten communication initialized"); + if (auto progress_flag = ranges::find(arguments_, "--progress"); progress_flag != arguments_.end()) + { + progressHandler = *ranges::next(progress_flag); + } +} + void EmscriptenCommunication::sendSliceUUID(const std::string& slice_uuid) const { spdlog::info("Slice UUID: {}", slice_uuid); @@ -21,7 +36,7 @@ void EmscriptenCommunication::sendPrintTimeMaterialEstimates() const void EmscriptenCommunication::sendProgress(double progress) const { - spdlog::info("Progress: {}", progress); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progressHandler, progress).c_str()); } void EmscriptenCommunication::sliceNext() @@ -31,3 +46,5 @@ void EmscriptenCommunication::sliceNext() }; } // namespace cura + +#endif // __EMSCRIPTEN__ From cf6ef1f9d9afaf02f0df8cf8e3521c6947692eff Mon Sep 17 00:00:00 2001 From: jellespijker Date: Fri, 2 Aug 2024 14:06:55 +0000 Subject: [PATCH 15/60] Applied clang-format. --- include/Application.h | 2 +- include/communication/CommandLine.h | 8 ++++---- include/communication/EmscriptenCommunication.h | 1 - include/utils/ExtrusionLine.h | 4 ++-- src/geometry/PartsView.cpp | 6 +++--- src/infill/ZigzagConnectorProcessor.cpp | 3 +-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/Application.h b/include/Application.h index 6884c56eae..d392586873 100644 --- a/include/Application.h +++ b/include/Application.h @@ -39,7 +39,7 @@ class Application : NoCopy * can assume that it is safe to access this without checking whether it is * initialised. */ - std::unique_ptr communication_ { nullptr }; + std::unique_ptr communication_{ nullptr }; /* * \brief The slice that is currently ongoing. diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 59d908d79e..15286e6049 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -158,10 +158,10 @@ class CommandLine : public Communication void sliceNext() override; protected: - /* - * \brief The command line arguments that the application was called with. - */ - std::vector arguments_; + /* + * \brief The command line arguments that the application was called with. + */ + std::vector arguments_; private: std::vector search_directories_; diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index d8590db21e..5bad3c85d4 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -25,7 +25,6 @@ class EmscriptenCommunication : public CommandLine void sendProgress(double progress) const override; void sliceNext() override; - }; } // namespace cura diff --git a/include/utils/ExtrusionLine.h b/include/utils/ExtrusionLine.h index 066a52e4f5..fa76eaca93 100644 --- a/include/utils/ExtrusionLine.h +++ b/include/utils/ExtrusionLine.h @@ -5,6 +5,8 @@ #ifndef UTILS_EXTRUSION_LINE_H #define UTILS_EXTRUSION_LINE_H +#include + #include #include #include @@ -13,8 +15,6 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" -#include - namespace cura { diff --git a/src/geometry/PartsView.cpp b/src/geometry/PartsView.cpp index a1d8fc1960..c1370895c5 100644 --- a/src/geometry/PartsView.cpp +++ b/src/geometry/PartsView.cpp @@ -3,12 +3,12 @@ #include "geometry/PartsView.h" -#include "geometry/Polygon.h" -#include "geometry/SingleShape.h" - #include #include +#include "geometry/Polygon.h" +#include "geometry/SingleShape.h" + namespace cura { diff --git a/src/infill/ZigzagConnectorProcessor.cpp b/src/infill/ZigzagConnectorProcessor.cpp index 2ea14d3953..7aadebc911 100644 --- a/src/infill/ZigzagConnectorProcessor.cpp +++ b/src/infill/ZigzagConnectorProcessor.cpp @@ -3,6 +3,7 @@ #include "infill/ZigzagConnectorProcessor.h" +#include #include #include "geometry/OpenPolyline.h" @@ -10,8 +11,6 @@ #include "geometry/Polygon.h" #include "geometry/Shape.h" -#include - using namespace cura; From c1bb0fee4e1c84c17afe123c39d392e8013cf6ab Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 5 Aug 2024 12:51:29 +0200 Subject: [PATCH 16/60] Add simple full on functionality for aux fan CURA-11849 --- src/LayerPlan.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1ef4b35bda..882f69bce5 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2006,6 +2006,15 @@ void LayerPlan::writeGCode(GCodeExport& gcode) constexpr bool wait = false; gcode.writeBedTemperatureCommand(mesh_group_settings.get("material_bed_temperature"), wait); } + if(mesh_group_settings.get("build_volume_fan_nr") != 0) + { + // The machine has a build volume fan. + if(layer_nr_ == mesh_group_settings.get("build_fan_full_layer")) + { + gcode.writeSpecificFanCommand(100, mesh_group_settings.get("build_volume_fan_nr")); + } + } + gcode.setZ(z_); From cc034ed460da514cf78a0a862d06bca9a4bf4bbc Mon Sep 17 00:00:00 2001 From: nallath Date: Mon, 5 Aug 2024 10:53:29 +0000 Subject: [PATCH 17/60] Applied clang-format. --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 882f69bce5..ee0b331396 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2006,10 +2006,10 @@ void LayerPlan::writeGCode(GCodeExport& gcode) constexpr bool wait = false; gcode.writeBedTemperatureCommand(mesh_group_settings.get("material_bed_temperature"), wait); } - if(mesh_group_settings.get("build_volume_fan_nr") != 0) + if (mesh_group_settings.get("build_volume_fan_nr") != 0) { // The machine has a build volume fan. - if(layer_nr_ == mesh_group_settings.get("build_fan_full_layer")) + if (layer_nr_ == mesh_group_settings.get("build_fan_full_layer")) { gcode.writeSpecificFanCommand(100, mesh_group_settings.get("build_volume_fan_nr")); } From 3463f1038180070a6ba7fdff40706a3f6934faa8 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Mon, 5 Aug 2024 15:29:45 +0200 Subject: [PATCH 18/60] Update conandata.yml --- conandata.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conandata.yml b/conandata.yml index c3ba739591..009c2aeb2a 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,9 +1,9 @@ -version: "5.8.0" +version: "5.9.0-alpha.0" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: - "arcus/5.3.1" requirements_plugins: - - "curaengine_grpc_definitions/0.2.1" + - "curaengine_grpc_definitions/(latest)@ultimaker/testing" requirements_cura_resources: - - "cura_resources/5.8.0" + - "cura_resources/(latest)@ultimaker/testing" From f291338378802e8993f636f9eba5a57b92eb5684 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 5 Aug 2024 16:09:04 +0200 Subject: [PATCH 19/60] Enable slice info callback for Emscripten communication This commit adds support for handling slice info callbacks through Emscripten, alongside progress callbacks. Additionally, functions to create slice info messages using rapidjson were implemented to prepare JSON data for slice information. Contribute to NP-327 --- CMakeLists.txt | 2 +- .../communication/EmscriptenCommunication.h | 7 +-- src/communication/CommandLine.cpp | 5 +- src/communication/EmscriptenCommunication.cpp | 59 +++++++++++++++---- stress_benchmark/stress_benchmark.cpp | 2 +- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bd6d1a6c0..0b6cbff243 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,7 +283,7 @@ if (CMAKE_CXX_PLATFORM_ID STREQUAL "emscripten") "SHELL:-sSINGLE_FILE=1" "SHELL:-sENVIRONMENT=web" "SHELL:-sERROR_ON_UNDEFINED_SYMBOLS=0" - "SHELL:-sWASM_BIGINT=1" +# "SHELL:-sWASM_BIGINT=1" "SHELL:-sSTACK_SIZE=196608" $<$:SHELL:-sASSERTIONS=2> $<$:SHELL:-sSAFE_HEAP=1> diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 5bad3c85d4..ef131ad349 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -14,14 +14,13 @@ class Settings; class EmscriptenCommunication : public CommandLine { std::string progressHandler; + std::string sliceInfoHandler; + + [[nodiscard]] std::string createSliceInfoMessage() const; public: EmscriptenCommunication(const std::vector& arguments); - void sendSliceUUID(const std::string& slice_uuid) const override; - - void sendPrintTimeMaterialEstimates() const override; - void sendProgress(double progress) const override; void sliceNext() override; diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 44c02c33cb..200a1b772a 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -192,12 +192,13 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } - else if (argument.find("--progress") == 0) +#ifdef __EMSCRIPTEN__ + else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb")) { - // Store progress handler name argument_index++; argument = arguments_[argument_index]; } +#endif // __EMSCRIPTEN__ else { spdlog::error("Unknown option: {}", argument); diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 6a3084e0e2..7b30734ece 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -1,16 +1,20 @@ // Copyright (c) 2024 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include +#include #ifdef __EMSCRIPTEN__ -#include "communication/EmscriptenCommunication.h" - #include +#include #include #include #include #include +#include "FffProcessor.h" +#include "communication/EmscriptenCommunication.h" + namespace cura { @@ -18,31 +22,64 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& : CommandLine(arguments) { spdlog::info("Emscripten communication initialized"); - if (auto progress_flag = ranges::find(arguments_, "--progress"); progress_flag != arguments_.end()) + if (auto progress_flag = ranges::find(arguments_, "--progress_cb"); progress_flag != arguments_.end()) { progressHandler = *ranges::next(progress_flag); } + if (auto slice_info_flag = ranges::find(arguments_, "--slice_info_cb"); slice_info_flag != arguments_.end()) + { + sliceInfoHandler = *ranges::next(slice_info_flag); + } } -void EmscriptenCommunication::sendSliceUUID(const std::string& slice_uuid) const +void EmscriptenCommunication::sendProgress(double progress) const { - spdlog::info("Slice UUID: {}", slice_uuid); + spdlog::info("Progress: {}", progress); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progressHandler, progress).c_str()); } -void EmscriptenCommunication::sendPrintTimeMaterialEstimates() const +std::string EmscriptenCommunication::createSliceInfoMessage() const { - spdlog::info("Print time and material estimates"); -} + // Construct a string with rapidjson containing the slice information + rapidjson::Document doc; + doc.SetObject(); + auto& allocator = doc.GetAllocator(); + rapidjson::Value time_estimates_json(rapidjson::kObjectType); -void EmscriptenCommunication::sendProgress(double progress) const -{ - emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progressHandler, progress).c_str()); + auto time_estimates = FffProcessor::getInstance()->getTotalPrintTimePerFeature(); + for (const auto& [feature, duration_idx] : std::vector>{ { "infill", PrintFeatureType::Infill }, + { "skin", PrintFeatureType::Skin }, + { "support", PrintFeatureType::Support }, + { "inner_wall", PrintFeatureType::InnerWall }, + { "move_combing", PrintFeatureType::MoveCombing }, + { "move_retraction", PrintFeatureType::MoveRetraction }, + { "outer_wall", PrintFeatureType::OuterWall }, + { "prime_tower", PrintFeatureType::PrimeTower }, + { "skirt_brim", PrintFeatureType::SkirtBrim }, + { "support_infill", PrintFeatureType::SupportInfill }, + { "support_interface", PrintFeatureType::SupportInterface } }) + { + rapidjson::Value feature_time_estimate(feature.c_str(), allocator); + rapidjson::Value feature_duration(time_estimates[static_cast(duration_idx)]); + time_estimates_json.AddMember(feature_time_estimate, feature_duration, allocator); + } + doc.AddMember("time_estimates", time_estimates_json, allocator); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + auto ret = buffer.GetString(); + spdlog::info("Slice info message: {}", ret); + return buffer.GetString(); } void EmscriptenCommunication::sliceNext() { spdlog::info("Slice next"); CommandLine::sliceNext(); + spdlog::info("Slice next done"); + auto slice_info = createSliceInfoMessage(); + spdlog::info("Slice info handler: {}", slice_info); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", sliceInfoHandler, slice_info).c_str()); }; } // namespace cura diff --git a/stress_benchmark/stress_benchmark.cpp b/stress_benchmark/stress_benchmark.cpp index cd3cd4ce33..ba4dc1d79d 100644 --- a/stress_benchmark/stress_benchmark.cpp +++ b/stress_benchmark/stress_benchmark.cpp @@ -195,7 +195,7 @@ void createAndWriteJson(const std::filesystem::path& out_file, double stress_lev { rapidjson::Document doc; doc.SetArray(); - rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); + rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); auto no_test_cases_obj = createRapidJSONObject(allocator, "Number of test cases", no_test_cases, "-", ""); doc.PushBack(no_test_cases_obj, allocator); From e3a14ab07a142ca2e2568625d69b96ceae9d1588 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 6 Aug 2024 12:30:15 +0200 Subject: [PATCH 20/60] Switch to smart pointers Replaced raw pointers with `std::shared_ptr` for better memory management. Standardized variable names to follow consistent naming conventions, improving code readability and maintainability. This was needed because the reference to Slice was otherwise out-of-scope when calling `Emscripten::sliceNext()` Contribute to NP-327 --- include/Application.h | 2 +- .../communication/EmscriptenCommunication.h | 7 +- src/Application.cpp | 1 + src/communication/CommandLine.cpp | 54 ++++++++-------- src/communication/EmscriptenCommunication.cpp | 64 +++++++++++++------ 5 files changed, 77 insertions(+), 51 deletions(-) diff --git a/include/Application.h b/include/Application.h index d392586873..b7a3a80863 100644 --- a/include/Application.h +++ b/include/Application.h @@ -46,7 +46,7 @@ class Application : NoCopy * * If no slice has started yet, this will be a nullptr. */ - Slice* current_slice_ = nullptr; + std::shared_ptr current_slice_{ nullptr }; /*! * \brief ThreadPool with lifetime tied to Application diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index ef131ad349..e98751abb3 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -9,14 +9,13 @@ namespace cura { -class Settings; class EmscriptenCommunication : public CommandLine { - std::string progressHandler; - std::string sliceInfoHandler; + std::string progress_handler_; + std::string slice_info_handler_; - [[nodiscard]] std::string createSliceInfoMessage() const; + [[nodiscard]] static std::string createSliceInfoMessage() ; public: EmscriptenCommunication(const std::vector& arguments); diff --git a/src/Application.cpp b/src/Application.cpp index f167825b11..da29281b3f 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -18,6 +18,7 @@ #include #include +#include "Slice.h" #include "communication/ArcusCommunication.h" //To connect via Arcus to the front-end. #include "communication/CommandLine.h" //To use the command line to slice stuff. #include "communication/EmscriptenCommunication.h" // To use Emscripten to slice stuff. diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 200a1b772a..493266ae8d 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -135,16 +135,16 @@ void CommandLine::sliceNext() num_mesh_groups++; } } - Slice slice(num_mesh_groups); - Application::getInstance().current_slice_ = &slice; + Application::getInstance().current_slice_ = std::make_shared(num_mesh_groups); + auto slice = Application::getInstance().current_slice_; size_t mesh_group_index = 0; - Settings* last_settings = &slice.scene.settings; + Settings* last_settings = &slice->scene.settings; - slice.scene.extruders.reserve(arguments_.size() >> 1); // Allocate enough memory to prevent moves. - slice.scene.extruders.emplace_back(0, &slice.scene.settings); // Always have one extruder. - ExtruderTrain* last_extruder = slice.scene.extruders.data(); + slice->scene.extruders.reserve(arguments_.size() >> 1); // Allocate enough memory to prevent moves. + slice->scene.extruders.emplace_back(0, &slice->scene.settings); // Always have one extruder. + ExtruderTrain* last_extruder = slice->scene.extruders.data(); bool force_read_parent = false; bool force_read_nondefault = false; @@ -164,7 +164,7 @@ void CommandLine::sliceNext() mesh_group_index++; FffProcessor::getInstance()->time_keeper.restart(); - last_settings = &slice.scene.mesh_groups[mesh_group_index].settings; + last_settings = &slice->scene.mesh_groups[mesh_group_index].settings; } catch (...) { @@ -253,12 +253,12 @@ void CommandLine::sliceNext() } // If this was the global stack, create extruders for the machine_extruder_count setting. - if (last_settings == &slice.scene.settings) + if (last_settings == &slice->scene.settings) { - const auto extruder_count = slice.scene.settings.get("machine_extruder_count"); - while (slice.scene.extruders.size() < extruder_count) + const auto extruder_count = slice->scene.settings.get("machine_extruder_count"); + while (slice->scene.extruders.size() < extruder_count) { - slice.scene.extruders.emplace_back(slice.scene.extruders.size(), &slice.scene.settings); + slice->scene.extruders.emplace_back(slice->scene.extruders.size(), &slice->scene.settings); } } // If this was an extruder stack, make sure that the extruder_nr setting is correct. @@ -271,13 +271,13 @@ void CommandLine::sliceNext() case 'e': { size_t extruder_nr = stoul(argument.substr(2)); - while (slice.scene.extruders.size() <= extruder_nr) // Make sure we have enough extruders up to the extruder_nr that the user wanted. + while (slice->scene.extruders.size() <= extruder_nr) // Make sure we have enough extruders up to the extruder_nr that the user wanted. { - slice.scene.extruders.emplace_back(extruder_nr, &slice.scene.settings); + slice->scene.extruders.emplace_back(extruder_nr, &slice->scene.settings); } - last_settings = &slice.scene.extruders[extruder_nr].settings_; + last_settings = &slice->scene.extruders[extruder_nr].settings_; last_settings->add("extruder_nr", argument.substr(2)); - last_extruder = &slice.scene.extruders[extruder_nr]; + last_extruder = &slice->scene.extruders[extruder_nr]; break; } case 'l': @@ -292,14 +292,14 @@ void CommandLine::sliceNext() const auto transformation = last_settings->get("mesh_rotation_matrix"); // The transformation applied to the model when loaded. - if (! loadMeshIntoMeshGroup(&slice.scene.mesh_groups[mesh_group_index], argument.c_str(), transformation, last_extruder->settings_)) + if (! loadMeshIntoMeshGroup(&slice->scene.mesh_groups[mesh_group_index], argument.c_str(), transformation, last_extruder->settings_)) { spdlog::error("Failed to load model: {}. (error number {})", argument, errno); exit(1); } else { - last_settings = &slice.scene.mesh_groups[mesh_group_index].meshes.back().settings_; + last_settings = &slice->scene.mesh_groups[mesh_group_index].meshes.back().settings_; } break; } @@ -321,7 +321,7 @@ void CommandLine::sliceNext() } case 'g': { - last_settings = &slice.scene.mesh_groups[mesh_group_index].settings; + last_settings = &slice->scene.mesh_groups[mesh_group_index].settings; break; } /* ... falls through ... */ @@ -419,19 +419,19 @@ void CommandLine::sliceNext() for (const auto& [setting_key, setting_value] : global_settings) { - slice.scene.settings.add(setting_key, setting_value); + slice->scene.settings.add(setting_key, setting_value); } for (const auto& [key, values] : extruder_settings) { const auto extruder_nr = std::stoi(key.substr(extruder_identifier.size())); - while (slice.scene.extruders.size() <= static_cast(extruder_nr)) + while (slice->scene.extruders.size() <= static_cast(extruder_nr)) { - slice.scene.extruders.emplace_back(extruder_nr, &slice.scene.settings); + slice->scene.extruders.emplace_back(extruder_nr, &slice->scene.settings); } for (const auto& [setting_key, setting_value] : values) { - slice.scene.extruders[extruder_nr].settings_.add(setting_key, setting_value); + slice->scene.extruders[extruder_nr].settings_.add(setting_key, setting_value); } } @@ -448,20 +448,20 @@ void CommandLine::sliceNext() const auto transformation = mesh_group.settings.get("mesh_rotation_matrix"); const auto extruder_nr = mesh_group.settings.get("extruder_nr"); - if (! loadMeshIntoMeshGroup(&mesh_group, model_name.c_str(), transformation, slice.scene.extruders[extruder_nr].settings_)) + if (! loadMeshIntoMeshGroup(&mesh_group, model_name.c_str(), transformation, slice->scene.extruders[extruder_nr].settings_)) { spdlog::error("Failed to load model: {}. (error number {})", model_name, errno); exit(1); } - slice.scene.mesh_groups.push_back(std::move(mesh_group)); + slice->scene.mesh_groups.push_back(std::move(mesh_group)); } for (const auto& [key, value] : limit_to_extruder) { const auto extruder_nr = std::stoi(value.substr(extruder_identifier.size())); if (extruder_nr >= 0) { - slice.scene.limit_to_extruder[key] = &slice.scene.extruders[extruder_nr]; + slice->scene.limit_to_extruder[key] = &slice->scene.extruders[extruder_nr]; } } @@ -492,11 +492,11 @@ void CommandLine::sliceNext() try { #endif // DEBUG - slice.scene.mesh_groups[mesh_group_index].finalize(); + slice->scene.mesh_groups[mesh_group_index].finalize(); spdlog::info("Loaded from disk in {:3}s\n", FffProcessor::getInstance()->time_keeper.restart()); // Start slicing. - slice.compute(); + slice->compute(); #ifndef DEBUG } catch (...) diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 7b30734ece..2af6f37a8c 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -1,19 +1,22 @@ // Copyright (c) 2024 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include -#include #ifdef __EMSCRIPTEN__ +#include "communication/EmscriptenCommunication.h" + #include #include +#include +#include #include #include #include #include +#include "Application.h" #include "FffProcessor.h" -#include "communication/EmscriptenCommunication.h" +#include "Slice.h" namespace cura { @@ -24,28 +27,33 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& spdlog::info("Emscripten communication initialized"); if (auto progress_flag = ranges::find(arguments_, "--progress_cb"); progress_flag != arguments_.end()) { - progressHandler = *ranges::next(progress_flag); + progress_handler_ = *ranges::next(progress_flag); } if (auto slice_info_flag = ranges::find(arguments_, "--slice_info_cb"); slice_info_flag != arguments_.end()) { - sliceInfoHandler = *ranges::next(slice_info_flag); + slice_info_handler_ = *ranges::next(slice_info_flag); } } void EmscriptenCommunication::sendProgress(double progress) const { - spdlog::info("Progress: {}", progress); - emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progressHandler, progress).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progress_handler_, progress).c_str()); } -std::string EmscriptenCommunication::createSliceInfoMessage() const +std::string EmscriptenCommunication::createSliceInfoMessage() { // Construct a string with rapidjson containing the slice information rapidjson::Document doc; - doc.SetObject(); auto& allocator = doc.GetAllocator(); - rapidjson::Value time_estimates_json(rapidjson::kObjectType); + doc.SetObject(); + + // Set the slice UUID + rapidjson::Value slice_uuid("slice_uuid", allocator); + rapidjson::Value uuid(Application::getInstance().instance_uuid_.c_str(), allocator); + doc.AddMember(slice_uuid, uuid, allocator); + // Set the time estimates + rapidjson::Value time_estimates_json(rapidjson::kObjectType); auto time_estimates = FffProcessor::getInstance()->getTotalPrintTimePerFeature(); for (const auto& [feature, duration_idx] : std::vector>{ { "infill", PrintFeatureType::Infill }, { "skin", PrintFeatureType::Skin }, @@ -59,27 +67,45 @@ std::string EmscriptenCommunication::createSliceInfoMessage() const { "support_infill", PrintFeatureType::SupportInfill }, { "support_interface", PrintFeatureType::SupportInterface } }) { - rapidjson::Value feature_time_estimate(feature.c_str(), allocator); + rapidjson::Value feature_time(feature.c_str(), allocator); rapidjson::Value feature_duration(time_estimates[static_cast(duration_idx)]); - time_estimates_json.AddMember(feature_time_estimate, feature_duration, allocator); + time_estimates_json.AddMember(feature_time, feature_duration, allocator); } doc.AddMember("time_estimates", time_estimates_json, allocator); + + // Set the material estimates + rapidjson::Value material_estimates_json(rapidjson::kObjectType); + const Scene& scene = Application::getInstance().current_slice_->scene; + + for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice_->scene.extruders.size(); extruder_nr++) + { + const double value = FffProcessor::getInstance()->getTotalFilamentUsed(static_cast(extruder_nr)); + spdlog::info("Extruder {} used {} [mm] of filament", extruder_nr, value); + rapidjson::Value extruder_id(fmt::format("{}", extruder_nr).c_str(), allocator); + rapidjson::Value extruder_material_estimate(value); + material_estimates_json.AddMember(extruder_id, extruder_material_estimate, allocator); + } + doc.AddMember("material_estimates", material_estimates_json, allocator); + + // Set the CureEngine information and slicing times + rapidjson::Value slicer_info_json(rapidjson::kObjectType); + rapidjson::Value slicer_version(CURA_ENGINE_VERSION, allocator); + slicer_info_json.AddMember("version", slicer_version, allocator); + // Add timekeeper info + doc.AddMember("slicer_info", slicer_info_json, allocator); + + // Serialize the JSON document to a string rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); + rapidjson::Writer writer(buffer); doc.Accept(writer); - auto ret = buffer.GetString(); - spdlog::info("Slice info message: {}", ret); return buffer.GetString(); } void EmscriptenCommunication::sliceNext() { - spdlog::info("Slice next"); CommandLine::sliceNext(); - spdlog::info("Slice next done"); auto slice_info = createSliceInfoMessage(); - spdlog::info("Slice info handler: {}", slice_info); - emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", sliceInfoHandler, slice_info).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", slice_info_handler_, slice_info).c_str()); }; } // namespace cura From c788fae665a7b93e17796d5f7ab3d04d0a90c01f Mon Sep 17 00:00:00 2001 From: jellespijker Date: Tue, 6 Aug 2024 10:30:46 +0000 Subject: [PATCH 21/60] 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 e98751abb3..3ab69e08ed 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -15,7 +15,7 @@ class EmscriptenCommunication : public CommandLine std::string progress_handler_; std::string slice_info_handler_; - [[nodiscard]] static std::string createSliceInfoMessage() ; + [[nodiscard]] static std::string createSliceInfoMessage(); public: EmscriptenCommunication(const std::vector& arguments); From ead62ed0ee4d24bdf7eab68a8258acb38dd97ffc Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 6 Aug 2024 15:56:15 +0200 Subject: [PATCH 22/60] Builds: Unbump grpc-definitions version. --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index 009c2aeb2a..f5da580fcb 100644 --- a/conandata.yml +++ b/conandata.yml @@ -4,6 +4,6 @@ requirements: requirements_arcus: - "arcus/5.3.1" requirements_plugins: - - "curaengine_grpc_definitions/(latest)@ultimaker/testing" + - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - "cura_resources/(latest)@ultimaker/testing" From b96cf27b3e2bba41c2a921f44f8646b98db91335 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 7 Aug 2024 10:00:33 +0200 Subject: [PATCH 23/60] Switch to shared_ptr for better memory management Replaced unique_ptr with shared_ptr in various components to facilitate shared ownership of resources and prevent manual memory management. This change affects communication objects, `Slice` instances, and related test cases to ensure smooth integration. Contribute to NP-327 --- include/Application.h | 2 +- src/Application.cpp | 8 ++++---- src/LayerPlan.cpp | 4 ++-- src/communication/ArcusCommunication.cpp | 16 ++++++++-------- src/communication/ArcusCommunicationPrivate.cpp | 4 ++-- src/communication/EmscriptenCommunication.cpp | 4 +--- tests/GCodeExportTest.cpp | 15 ++++----------- tests/LayerPlanTest.cpp | 3 +-- tests/arcus/ArcusCommunicationPrivateTest.cpp | 4 +--- tests/integration/SlicePhaseTest.cpp | 2 +- tests/settings/SettingsTest.cpp | 8 ++++---- 11 files changed, 29 insertions(+), 41 deletions(-) diff --git a/include/Application.h b/include/Application.h index b7a3a80863..7cee5e654b 100644 --- a/include/Application.h +++ b/include/Application.h @@ -39,7 +39,7 @@ class Application : NoCopy * can assume that it is safe to access this without checking whether it is * initialised. */ - std::unique_ptr communication_{ nullptr }; + std::shared_ptr communication_{ nullptr }; /* * \brief The slice that is currently ongoing. diff --git a/src/Application.cpp b/src/Application.cpp index da29281b3f..319c55e544 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -101,9 +101,9 @@ void Application::connect() } } - auto arcus_communication = std::make_unique(); + auto arcus_communication = std::make_shared(); arcus_communication->connect(ip, port); - communication_ = std::move(arcus_communication); + communication_ = arcus_communication; } #endif // ARCUS @@ -216,9 +216,9 @@ void Application::slice() arguments.emplace_back(argv_[argument_index]); } #ifdef __EMSCRIPTEN__ - communication_ = std::make_unique(arguments); + communication_ = std::make_shared(arguments); #else - communication_ = std::make_unique(arguments); + communication_ = std::make_shared(arguments); #endif } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index b39043b8ff..c37b2c0cc5 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1983,7 +1983,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) void LayerPlan::writeGCode(GCodeExport& gcode) { - auto& communication = Application::getInstance().communication_; + auto communication = Application::getInstance().communication_; communication->setLayerForSend(layer_nr_); communication->sendCurrentPosition(gcode.getPositionXY()); gcode.setLayerNr(layer_nr_); @@ -2536,7 +2536,7 @@ bool LayerPlan::writePathWithCoasting( Point2LL prev_pt = gcode.getPositionXY(); { // write normal extrude path: - auto& communication = Application::getInstance().communication_; + auto 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); diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 0f0fd4e6d3..27e0e19b51 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -520,7 +520,7 @@ void ArcusCommunication::sliceNext() // Handle the main Slice message. const cura::proto::Slice* slice_message = dynamic_cast(message.get()); // See if the message is of the message type Slice. Returns nullptr otherwise. - if (! slice_message) + if (slice_message == nullptr) { return; } @@ -553,15 +553,15 @@ void ArcusCommunication::sliceNext() } #endif // ENABLE_PLUGINS - Slice slice(slice_message->object_lists().size()); - Application::getInstance().current_slice_ = &slice; + auto slice = std::make_shared(slice_message->object_lists().size()); + Application::getInstance().current_slice_ = slice; private_data->readGlobalSettingsMessage(slice_message->global_settings()); private_data->readExtruderSettingsMessage(slice_message->extruders()); // Broadcast the settings to the plugins slots::instance().broadcast(*slice_message); - const size_t extruder_count = slice.scene.extruders.size(); + const size_t extruder_count = slice->scene.extruders.size(); // For each setting, register what extruder it should be obtained from (if this is limited to an extruder). for (const cura::proto::SettingExtruder& setting_extruder : slice_message->limit_to_extruder()) @@ -572,8 +572,8 @@ void ArcusCommunication::sliceNext() // If it's -1 it should be ignored as per the spec. Let's also ignore it if it's beyond range. continue; } - ExtruderTrain& extruder = slice.scene.extruders[setting_extruder.extruder()]; - slice.scene.limit_to_extruder.emplace(setting_extruder.name(), &extruder); + ExtruderTrain& extruder = slice->scene.extruders[setting_extruder.extruder()]; + slice->scene.limit_to_extruder.emplace(setting_extruder.name(), &extruder); } // Load all mesh groups, meshes and their settings. @@ -584,9 +584,9 @@ void ArcusCommunication::sliceNext() } spdlog::debug("Done reading Slice message."); - if (! slice.scene.mesh_groups.empty()) + if (! slice->scene.mesh_groups.empty()) { - slice.compute(); + slice->compute(); FffProcessor::getInstance()->finalize(); flushGCode(); sendPrintTimeMaterialEstimates(); diff --git a/src/communication/ArcusCommunicationPrivate.cpp b/src/communication/ArcusCommunicationPrivate.cpp index 81101dc9b2..44621918ca 100644 --- a/src/communication/ArcusCommunicationPrivate.cpp +++ b/src/communication/ArcusCommunicationPrivate.cpp @@ -47,7 +47,7 @@ std::shared_ptr ArcusCommunication::Private::getOptimized void ArcusCommunication::Private::readGlobalSettingsMessage(const proto::SettingList& global_settings_message) { - Slice* slice = Application::getInstance().current_slice_; + auto slice = Application::getInstance().current_slice_; for (const cura::proto::Setting& setting_message : global_settings_message.settings()) { slice->scene.settings.add(setting_message.name(), setting_message.value()); @@ -57,7 +57,7 @@ void ArcusCommunication::Private::readGlobalSettingsMessage(const proto::Setting void ArcusCommunication::Private::readExtruderSettingsMessage(const google::protobuf::RepeatedPtrField& extruder_messages) { // Make sure we have enough extruders added currently. - Slice* slice = Application::getInstance().current_slice_; + auto slice = Application::getInstance().current_slice_; const size_t extruder_count = slice->scene.settings.get("machine_extruder_count"); for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 2af6f37a8c..b6a3260e28 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -87,11 +87,9 @@ std::string EmscriptenCommunication::createSliceInfoMessage() } doc.AddMember("material_estimates", material_estimates_json, allocator); - // Set the CureEngine information and slicing times + // Set CureEngine information rapidjson::Value slicer_info_json(rapidjson::kObjectType); rapidjson::Value slicer_version(CURA_ENGINE_VERSION, allocator); - slicer_info_json.AddMember("version", slicer_version, allocator); - // Add timekeeper info doc.AddMember("slicer_info", slicer_info_json, allocator); // Serialize the JSON document to a string diff --git a/tests/GCodeExportTest.cpp b/tests/GCodeExportTest.cpp index e2d7ef15e0..aa5d0620b0 100644 --- a/tests/GCodeExportTest.cpp +++ b/tests/GCodeExportTest.cpp @@ -38,7 +38,7 @@ class GCodeExportTest : public testing::Test * Mock away the communication channel where layer data is output by this * class. */ - MockCommunication* mock_communication; + std::shared_ptr mock_communication; void SetUp() override { @@ -68,15 +68,13 @@ class GCodeExportTest : public testing::Test gcode.machine_name_ = "Your favourite 3D printer"; // Set up a scene so that we may request settings. - Application::getInstance().current_slice_ = new Slice(1); - mock_communication = new MockCommunication(); + Application::getInstance().current_slice_ = std::make_shared(1); + mock_communication = std::make_shared(); Application::getInstance().communication_ = mock_communication; } void TearDown() override { - delete Application::getInstance().current_slice_; - delete Application::getInstance().communication_; Application::getInstance().communication_ = nullptr; } }; @@ -224,12 +222,7 @@ class GriffinHeaderTest : public testing::TestWithParam gcode.machine_name_ = "Your favourite 3D printer"; // Set up a scene so that we may request settings. - Application::getInstance().current_slice_ = new Slice(0); - } - - void TearDown() override - { - delete Application::getInstance().current_slice_; + Application::getInstance().current_slice_ = std::make_shared(0); } }; // NOLINTEND(misc-non-private-member-variables-in-classes) diff --git a/tests/LayerPlanTest.cpp b/tests/LayerPlanTest.cpp index da6a783644..112f3d457f 100644 --- a/tests/LayerPlanTest.cpp +++ b/tests/LayerPlanTest.cpp @@ -80,7 +80,7 @@ class LayerPlanTest : public testing::Test SliceDataStorage* setUpStorage() { constexpr size_t num_mesh_groups = 1; - Application::getInstance().current_slice_ = new Slice(num_mesh_groups); + Application::getInstance().current_slice_ = std::make_shared(num_mesh_groups); // Define all settings in the mesh group. The extruder train and model settings will fall back on that then. settings = &Application::getInstance().current_slice_->scene.current_mesh_group->settings; @@ -228,7 +228,6 @@ class LayerPlanTest : public testing::Test void TearDown() override { delete storage; - delete Application::getInstance().current_slice_; } }; diff --git a/tests/arcus/ArcusCommunicationPrivateTest.cpp b/tests/arcus/ArcusCommunicationPrivateTest.cpp index 12652958ca..40b651250a 100644 --- a/tests/arcus/ArcusCommunicationPrivateTest.cpp +++ b/tests/arcus/ArcusCommunicationPrivateTest.cpp @@ -34,15 +34,13 @@ class ArcusCommunicationPrivateTest : public testing::Test { instance = new ArcusCommunication::Private(); instance->socket = new MockSocket(); - Application::getInstance().current_slice_ = new Slice(GK_TEST_NUM_MESH_GROUPS); + Application::getInstance().current_slice_ = std::make_shared(GK_TEST_NUM_MESH_GROUPS); } void TearDown() override { delete instance->socket; delete instance; - - delete Application::getInstance().current_slice_; } /* diff --git a/tests/integration/SlicePhaseTest.cpp b/tests/integration/SlicePhaseTest.cpp index cfb9557024..763873bcd8 100644 --- a/tests/integration/SlicePhaseTest.cpp +++ b/tests/integration/SlicePhaseTest.cpp @@ -31,7 +31,7 @@ class SlicePhaseTest : public testing::Test Application::getInstance().startThreadPool(); // Set up a scene so that we may request settings. - Application::getInstance().current_slice_ = new Slice(1); + Application::getInstance().current_slice_ = std::make_shared(1); // And a few settings that we want to default. Scene& scene = Application::getInstance().current_slice_->scene; diff --git a/tests/settings/SettingsTest.cpp b/tests/settings/SettingsTest.cpp index 0c6c98f458..59a60a3528 100644 --- a/tests/settings/SettingsTest.cpp +++ b/tests/settings/SettingsTest.cpp @@ -87,8 +87,8 @@ class Slice; // Forward declaration to save some time compiling. TEST_F(SettingsTest, AddSettingExtruderTrain) { // Add a slice with some extruder trains. - std::shared_ptr current_slice = std::make_shared(0); - Application::getInstance().current_slice_ = current_slice.get(); + auto current_slice = std::make_shared(0); + Application::getInstance().current_slice_ = current_slice; current_slice->scene.extruders.emplace_back(0, nullptr); current_slice->scene.extruders.emplace_back(1, nullptr); current_slice->scene.extruders.emplace_back(2, nullptr); @@ -223,7 +223,7 @@ TEST_F(SettingsTest, OverwriteSetting) TEST_F(SettingsTest, Inheritance) { std::shared_ptr current_slice = std::make_shared(0); - Application::getInstance().current_slice_ = current_slice.get(); + Application::getInstance().current_slice_ = current_slice; const std::string value = "To be frank, I'd have to change my name."; Settings parent; @@ -240,7 +240,7 @@ TEST_F(SettingsTest, Inheritance) TEST_F(SettingsTest, LimitToExtruder) { std::shared_ptr current_slice = std::make_shared(0); - Application::getInstance().current_slice_ = current_slice.get(); + Application::getInstance().current_slice_ = current_slice; current_slice->scene.extruders.emplace_back(0, nullptr); current_slice->scene.extruders.emplace_back(1, nullptr); current_slice->scene.extruders.emplace_back(2, nullptr); From 922645a4bcf5a6eff3854500892af139b2604115 Mon Sep 17 00:00:00 2001 From: Hello1024 Date: Thu, 8 Aug 2024 17:41:03 +0100 Subject: [PATCH 24/60] Apply suggestions from code review Co-authored-by: Remco Burema <41987080+rburema@users.noreply.github.com> --- src/FffGcodeWriter.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 5374de1d3d..b62b451b65 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2026,7 +2026,7 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& { OpenLinesSet candidate_lines; - const int numAngles = 16; + constexpr int numAngles = 16; const double angleStep = 180.0 / numAngles; // Step size between angles for (int i = 0; i < numAngles; ++i) @@ -2052,7 +2052,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. - auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](Point2LL p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { @@ -2075,8 +2075,8 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin if (front_match && back_match) { - auto [front_line_index, front_point_index] = *front_match; - auto [back_line_index, back_point_index] = *back_match; + const auto& [front_line_index, front_point_index] = *front_match; + const auto& [back_line_index, back_point_index] = *back_match; if (front_line_index == back_line_index) { @@ -2156,7 +2156,9 @@ void wall_tool_paths2lines(const std::vector>& w { const Polygon& poly = c.toPolygon(); if (c.is_closed_) + { result.push_back(poly.toPseudoOpenPolyline()); + } } } } @@ -2197,11 +2199,15 @@ void addExtraLinesToSupportSurfacesAbove( const auto enabled = mesh.settings.get("extra_infill_lines_to_support_skins"); if (enabled == EExtraInfillLinesToSupportSkins::NONE) + { return; + } const size_t skin_layer_nr = gcode_layer.getLayerNr() + 1 + mesh.settings.get("skin_edge_support_layers"); if (skin_layer_nr >= mesh.layers.size()) + { return; + } OpenLinesSet printed_lines_on_layer_above; for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) @@ -2243,7 +2249,9 @@ void addExtraLinesToSupportSurfacesAbove( if (enabled == EExtraInfillLinesToSupportSkins::WALLS_AND_LINES) { for (const Polygon& poly : skin_polygons) + { printed_lines_on_layer_above.push_back(poly.toPseudoOpenPolyline()); + } printed_lines_on_layer_above.push_back(skin_lines); } } @@ -2262,14 +2270,20 @@ void addExtraLinesToSupportSurfacesAbove( for (auto it = copy.begin(); it != copy.end(); ++it, ++orig_it) { if (it > copy.begin()) + { *orig_it += normal(*(it - 1) - *(it), infill_line_width / 2); + } if (it < copy.end() - 1) + { *orig_it += normal(*(it + 1) - *(it), infill_line_width / 2); + } } } if (printed_lines_on_layer_above.empty()) + { return; + } // What shape is the supporting infill? OpenLinesSet support_lines; @@ -2285,7 +2299,9 @@ void addExtraLinesToSupportSurfacesAbove( // Turn the lines into a giant shape. Shape supported_area = support_lines.offset(infill_line_width / 2); if (supported_area.empty()) + { return; + } // invert the supported_area by adding one huge polygon around the outside supported_area.push_back(AABB{ supported_area }.toPolygon()); @@ -2310,7 +2326,9 @@ void addExtraLinesToSupportSurfacesAbove( { size_t idx = expanded_inv_supported_area.findInside(point); if (idx == NO_INDEX) + { continue; + } map[idx].push_back(point); } From 6aeada82ac1b439c1d514b50b12b44e7b08b72ab Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Thu, 8 Aug 2024 18:42:41 +0100 Subject: [PATCH 25/60] Address review comments --- src/FffGcodeWriter.cpp | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 973fc3e844..5e534cc123 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -41,6 +41,7 @@ namespace cura { +constexpr coord_t EPSILON = 5; FffGcodeWriter::FffGcodeWriter() : max_object_height(0) @@ -2003,15 +2004,14 @@ void getLinesForArea(OpenLinesSet& result_lines, const Shape& area, const AngleD // code seems like the best way. Infill infill_comp(EFillMethod::LINES, false, false, area, line_width, line_width, 0, 1, angle, 0, 0, 0, 0); - infill_comp.generate(unused_skin_paths, unused_skin_polygons, candidate_lines, {}, 0, SectionType::SKIN); + infill_comp.generate(unused_skin_paths, unused_skin_polygons, candidate_lines, {}, 0, SectionType::INFILL); // Select only lines which are needed to support points for (const auto& line : candidate_lines) { - const Shape line_shape = OpenLinesSet(line).offset(line_width / 2); for (const auto& point : points) { - if (line_shape.inside(point)) + if (LinearAlg2D::getDist2FromLineSegment(line.front(), point, line.back()) <= (line_width / 2) * (line_width / 2)) { result_lines.push_back(line); break; @@ -2026,17 +2026,35 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& { OpenLinesSet candidate_lines; - const int numAngles = 16; - const double angleStep = 180.0 / numAngles; // Step size between angles + struct CompareAngles + { + bool operator()(const AngleDegrees& a, const AngleDegrees& b) const + { + constexpr double small_angle = 5; + if (std::fmod(a - b + 360, 180) < small_angle) + { + return false; // Consider them as equal (near duplicates) + } + return (a < b); + } + }; + std::set candidate_angles; + + // heuristic that usually chooses a goodish angle + for (size_t i = 1; i < points.size(); i *= 2) + { + candidate_angles.insert(angle(points[i] - points[i / 2])); + } + + candidate_angles.insert({ 0, 90 }); - for (int i = 0; i < numAngles; ++i) + for (const auto& angle : candidate_angles) { - const AngleDegrees angle{ i * angleStep }; candidate_lines.clear(); getLinesForArea(candidate_lines, area, angle, points, line_width); if (candidate_lines.length() < result_lines.length() || result_lines.length() == 0) { - result_lines = candidate_lines; + result_lines = std::move(candidate_lines); } } } @@ -2061,7 +2079,7 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, infill_lines[i][j - 1], infill_lines[i][j]); int64_t dist = vSize2(p - closest_here); - if (dist < 25) // rounding + if (dist < EPSILON * EPSILON) // rounding { return std::make_tuple(i, j); } @@ -2295,7 +2313,7 @@ void addExtraLinesToSupportSurfacesAbove( 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(-10); + 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, @@ -2322,7 +2340,7 @@ void addExtraLinesToSupportSurfacesAbove( const PointsSet& points = pair.second; OpenLinesSet result_lines; - getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2 + 10), points, infill_line_width); + getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2 + EPSILON), points, infill_line_width); for (const auto& line : part.getOwnInfillArea().intersection(result_lines)) { From fb56e157ded5534217e5c0d598a94c7fd7c36550 Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Thu, 8 Aug 2024 19:36:41 +0100 Subject: [PATCH 26/60] Fix issue that only occurs with 3 or more infill_multiplier https://github.com/Ultimaker/Cura/pull/19446#pullrequestreview-2224598355 --- src/FffGcodeWriter.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index f276be9090..2034496dd1 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2311,6 +2311,11 @@ void addExtraLinesToSupportSurfacesAbove( { support_lines.push_back(poly.toPseudoOpenPolyline()); } + for (const auto& poly : infill_polygons) + { + support_lines.push_back(poly.toPseudoOpenPolyline()); + } + // Infill walls can support the layer above wall_tool_paths2lines(wall_tool_paths, support_lines); From f5b382b5c3f0f84638adef8d4fa91314d8b00efc Mon Sep 17 00:00:00 2001 From: Oliver Mattos Date: Thu, 8 Aug 2024 19:41:50 +0100 Subject: [PATCH 27/60] Address review comments (style) --- src/FffGcodeWriter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 2034496dd1..a10a07f425 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2623,7 +2623,9 @@ bool FffGcodeWriter::processSingleLayerInfill( && pattern != EFillMethod::CONCENTRIC // Doesn't handle 'holes' in infill lines very well && pattern != EFillMethod::CROSS // Ditto && pattern != EFillMethod::CROSS_3D) // Ditto + { addExtraLinesToSupportSurfacesAbove(infill_lines, infill_polygons, wall_tool_paths, part, infill_line_width, gcode_layer, mesh); + } const bool walls_generated = std::any_of( wall_tool_paths.cbegin(), From eb3eda68323f8dbfa941bc0760747c5736985def Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 15 Aug 2024 10:33:01 +0200 Subject: [PATCH 28/60] Document EmscriptenCommunication class. Added detailed documentation comments for the EmscriptenCommunication class, including descriptions for member variables and methods. This improves code readability and ease of maintenance. Contribute to NP-327 --- .../communication/EmscriptenCommunication.h | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 3ab69e08ed..225ff4efd3 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -10,22 +10,44 @@ namespace cura { +/** + * \class EmscriptenCommunication + * \brief A class for handling communication in an Emscripten environment. + * + * This class extends the CommandLine class and provides specific implementations + * for sending progress and handling slice information in an Emscripten environment. + */ class EmscriptenCommunication : public CommandLine { - std::string progress_handler_; - std::string slice_info_handler_; + std::string progress_handler_; ///< Handler for progress messages. + std::string slice_info_handler_; ///< Handler for slice information messages. + /** + * \brief Creates a message containing slice information. + * \return A string containing the slice information message. + */ [[nodiscard]] static std::string createSliceInfoMessage(); public: + /** + * \brief Constructor for EmscriptenCommunication. + * \param arguments A vector of strings containing the command line arguments. + */ EmscriptenCommunication(const std::vector& arguments); + /** + * \brief Sends the progress of the current operation. + * \param progress A double representing the progress percentage. + */ void sendProgress(double progress) const override; + /** + * \brief Initiates the slicing of the next item. + */ void sliceNext() override; }; } // namespace cura #endif // __EMSCRIPTEN__ -#endif // EMSCRIPTENCOMMUNICATION_H +#endif // EMSCRIPTENCOMMUNICATION_H \ No newline at end of file From 9b4a91377d0c9c08bc1cddc18c17a9500e061478 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 15 Aug 2024 10:37:28 +0200 Subject: [PATCH 29/60] Apply review suggestions Contribute to NP-327 --- include/Application.h | 4 ++-- include/communication/EmscriptenCommunication.h | 1 + src/communication/EmscriptenCommunication.cpp | 2 +- stress_benchmark/stress_benchmark.cpp | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/Application.h b/include/Application.h index 7cee5e654b..15a9b94bb2 100644 --- a/include/Application.h +++ b/include/Application.h @@ -39,14 +39,14 @@ class Application : NoCopy * can assume that it is safe to access this without checking whether it is * initialised. */ - std::shared_ptr communication_{ nullptr }; + std::shared_ptr communication_; /* * \brief The slice that is currently ongoing. * * If no slice has started yet, this will be a nullptr. */ - std::shared_ptr current_slice_{ nullptr }; + std::shared_ptr current_slice_; /*! * \brief ThreadPool with lifetime tied to Application diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 225ff4efd3..fc2a6897df 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -19,6 +19,7 @@ namespace cura */ class EmscriptenCommunication : public CommandLine { +private: std::string progress_handler_; ///< Handler for progress messages. std::string slice_info_handler_; ///< Handler for slice information messages. diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index b6a3260e28..140f47316d 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -87,7 +87,7 @@ std::string EmscriptenCommunication::createSliceInfoMessage() } doc.AddMember("material_estimates", material_estimates_json, allocator); - // Set CureEngine information + // Set CuraEngine information rapidjson::Value slicer_info_json(rapidjson::kObjectType); rapidjson::Value slicer_version(CURA_ENGINE_VERSION, allocator); doc.AddMember("slicer_info", slicer_info_json, allocator); diff --git a/stress_benchmark/stress_benchmark.cpp b/stress_benchmark/stress_benchmark.cpp index ba4dc1d79d..cd3cd4ce33 100644 --- a/stress_benchmark/stress_benchmark.cpp +++ b/stress_benchmark/stress_benchmark.cpp @@ -195,7 +195,7 @@ void createAndWriteJson(const std::filesystem::path& out_file, double stress_lev { rapidjson::Document doc; doc.SetArray(); - rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); + rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); auto no_test_cases_obj = createRapidJSONObject(allocator, "Number of test cases", no_test_cases, "-", ""); doc.PushBack(no_test_cases_obj, allocator); From da401d661e78b9f534f095c52acbe40364c5122d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 14:18:55 +0200 Subject: [PATCH 30/60] Integrate gradual flow code CURA-12096 --- include/LayerPlan.h | 2 + include/gradual_flow/boost_tags.h | 135 ++++++++ include/gradual_flow/concepts.h | 127 +++++++ include/gradual_flow/gcode_path.h | 451 +++++++++++++++++++++++++ include/gradual_flow/point_container.h | 134 ++++++++ include/gradual_flow/processor.h | 100 ++++++ include/gradual_flow/utils.h | 85 +++++ src/FffGcodeWriter.cpp | 2 + src/LayerPlan.cpp | 11 +- 9 files changed, 1045 insertions(+), 2 deletions(-) create mode 100644 include/gradual_flow/boost_tags.h create mode 100644 include/gradual_flow/concepts.h create mode 100644 include/gradual_flow/gcode_path.h create mode 100644 include/gradual_flow/point_container.h create mode 100644 include/gradual_flow/processor.h create mode 100644 include/gradual_flow/utils.h diff --git a/include/LayerPlan.h b/include/LayerPlan.h index d81bb9c437..7833e0c8e9 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -778,6 +778,8 @@ class LayerPlan : public NoCopy */ void applyBackPressureCompensation(); + void applyGradualFlow(); + private: /*! * \brief Compute the preferred or minimum combing boundary diff --git a/include/gradual_flow/boost_tags.h b/include/gradual_flow/boost_tags.h new file mode 100644 index 0000000000..9e2273acd5 --- /dev/null +++ b/include/gradual_flow/boost_tags.h @@ -0,0 +1,135 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_BOOST_TAGS_H +#define GRADUAL_FLOW_BOOST_TAGS_H + + +#ifndef INFILL_BOOST_TAGS_H +#define INFILL_BOOST_TAGS_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "gradual_flow/concepts.h" +#include "gradual_flow/point_container.h" + +namespace boost::geometry::traits +{ + +template<> +struct tag +{ + using type = point_tag; +}; + +template<> +struct dimension : boost::mpl::int_<2> +{ +}; + +template<> +struct coordinate_type +{ + using type = ClipperLib::cInt; +}; + +template<> +struct coordinate_system +{ + using type = boost::geometry::cs::cartesian; +}; + +template +struct access +{ + static_assert(Index < 2, "Out of range"); + using Point = ClipperLib::IntPoint; + using CoordinateType = typename coordinate_type::type; + constexpr static inline CoordinateType get(Point const& p) noexcept + { + return Index == 0 ? p.X : p.Y; + } + + constexpr static inline void set(Point& p, CoordinateType const& value) noexcept + { + if (Index == 0) + { + p.X = value; + } + else + { + p.Y = value; + } + } +}; + +template<> +struct tag> +{ + using type = linestring_tag; +}; + +template<> +struct point_order> +{ + static const order_selector value = clockwise; +}; + +template<> +struct closure> +{ + static const closure_selector value = open; +}; + +template<> +struct tag> +{ + using type = ring_tag; +}; + +template<> +struct point_order> +{ + static const order_selector value = counterclockwise; +}; + +template<> +struct closure> +{ + static const closure_selector value = open; +}; + +template<> +struct tag> +{ + using type = ring_tag; +}; + +template<> +struct point_order +{ + static const order_selector value = counterclockwise; +}; + +template<> +struct closure +{ + static const closure_selector value = open; +}; + +template<> +struct tag +{ + using type = ring_tag; +}; + +} // namespace boost::geometry::traits + +#endif // GRADUAL_FLOW_BOOST_TAGS_H diff --git a/include/gradual_flow/concepts.h b/include/gradual_flow/concepts.h new file mode 100644 index 0000000000..923157ada7 --- /dev/null +++ b/include/gradual_flow/concepts.h @@ -0,0 +1,127 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_CONCEPTS_H +#define GRADUAL_FLOW_CONCEPTS_H + +#include + +#if __has_include() +#include +#elif __has_include() +#include +#define USE_EXPERIMENTAL_CONCEPTS +#endif + +#include +#include + +namespace cura::gradual_flow +{ +enum class direction +{ + NA, + CW, + CCW +}; + +namespace concepts +{ + +template +concept closable = requires(T t) +{ + requires ranges::convertible_to; +}; + +template +concept is_closed_point_container = closable && requires(T t) +{ + t.is_closed == true; +}; + +template +concept is_open_point_container = closable && requires(T t) +{ + t.is_closed == false; +}; + +template +concept directional = requires(T t) +{ + requires std::is_same_v; +}; + +template +concept is_clockwise_point_container = directional && requires(T t) +{ + t.winding == direction::CW; +}; + +template +concept is_counterclockwise_point_container = directional && requires(T t) +{ + t.winding == direction::CCW; +}; + +template +concept point2d_named = requires(T point) +{ + point.X; + point.Y; +}; + +/*! + * @brief A 2D point, defined either as a named object with X and Y attributes, or as a range of two integral values. + * @details This concept is used to check if a type is a 2D point. A 2D point is a type that has a X and Y member or a type that is a range of integral types with a size of 2. + * @tparam T Type to check + */ +template +concept point2d = point2d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); + +template +concept point3d_named = requires(T point) +{ + point.x; + point.y; + point.z; +}; + +/*! + * @brief A 3D point, defined either as a named object with x, y, and z attributes, or as a range of three integral values. + * @details This concept is used to check if a type is a 3D point. A 3D point is a type that has a x, y and z member or a type that is a range of integral types with a size of 3. + * @tparam T Type to check + */ +template +concept point3d = point3d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); + +template +concept point_named = point2d_named || point3d_named; + +/*! + * @brief Either a Point2D or a Point3D + * @details This concept is used to check if a type is a point. A point is a type that is a 2D or 3D point. + * @tparam T Type to check + */ +template +concept point = point2d || point3d; + +template +concept point_ranged = point && ! point2d_named && ! point3d_named; + +template +concept polyline = ranges::range && is_open_point_container && point; + +template +concept polygon = ranges::range && is_closed_point_container && point; + +template +concept polygons = ranges::range && polygon; + +template +concept poly_range = polygon || polyline; + +} // namespace concepts +} // namespace cura::gradual_flow + +#endif // GRADUAL_FLOW_CONCEPTS_H diff --git a/include/gradual_flow/gcode_path.h b/include/gradual_flow/gcode_path.h new file mode 100644 index 0000000000..56c278dbf0 --- /dev/null +++ b/include/gradual_flow/gcode_path.h @@ -0,0 +1,451 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_GCODE_PATH_H +#define GRADUAL_FLOW_GCODE_PATH_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "gradual_flow/point_container.h" +#include "gradual_flow/utils.h" +#include "pathPlanning/GCodePath.h" + +namespace cura::gradual_flow +{ + +enum class FlowState +{ + STABLE, + TRANSITION, + UNDEFINED +}; + +struct GCodePath +{ + const cura::GCodePath* original_gcode_path_data; + geometry::polyline<> points; + double speed{ targetSpeed() }; // um/s + double flow_{ extrusionVolumePerMm() * speed }; // um/s + double total_length{ totalLength() }; // um + + double targetSpeed() const // um/s + { + return original_gcode_path_data->config.speed_derivatives.speed * original_gcode_path_data->speed_factor * 1e3; + } + + /* + * Returns if the path is a travel move. + * + * @return `true` If the path is a travel move, `false` otherwise + */ + bool isTravel() const + { + return targetFlow() <= 0; + } + + /* + * Returns if the path is a retract move. + * + * @return `true` If the path is a retract move, `false` otherwise + */ + bool isRetract() const + { + return original_gcode_path_data->retract; + } + + /* + * Returns the extrusion volume per um of the path. + * + * @return The extrusion volume per um of the path in um^3/um + */ + double extrusionVolumePerMm() const // um^3/um + { + return original_gcode_path_data->flow * original_gcode_path_data->config.line_width * original_gcode_path_data->config.layer_thickness * original_gcode_path_data->flow; + } + + /* + * Returns the extrusion volume of the path. + * + * @return The extrusion volume of the path in um^3/s + */ + double flow() const // um^3/s + { + return flow_; + } + + /* + * Returns the target extrusion volume of the path. + * + * @return The target extrusion volume of the path in um^3/s + */ + double targetFlow() const // um^3/s + { + return extrusionVolumePerMm() * targetSpeed(); + } + + /* + * Returns the path as an SVG path data string. + * + * @return the SVG path data string + */ + std::string toSvgPathData() const + { + std::string path_data; + auto is_first_point = true; + 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); + is_first_point = false; + } + return path_data; + } + + /* + * Returns the path as an SVG path-element. + * + * @return the SVG path + */ + std::string toSvgPath() + { + const auto path_data = toSvgPathData(); + + if (isTravel()) + { + return fmt::format("", path_data); + } + + const auto [r, g, b] = gradual_flow::utils::hsvToRgb(flow() * .00000003, 100., 100.); + const auto color = fmt::format("rgb({},{},{})", r, g, b); + return fmt::format("", path_data, color); + } + + /* + * Returns the total length of the path. + * + * @return the length in um + */ + double totalLength() const // um + { + double path_length = 0; + 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; + } + + /* + * Returns the total duration of the path. + * + * @return the duration in seconds + */ + double totalDuration() const // s + { + return total_length / speed; + } + + /* + * Splits either the beginning or the end of the path into a new path. + * + * @param partition_duration duration of the partitioned paths in s + * @param partition_speed speed of the partitioned paths in um/s + * @param direction + * @return a tuple of the partitioned path and the remaining path, the + * remaining path can possibly be empty if the duration of the + * partitioned path is equal or longer than the duration of the original + * path + */ + std::tuple, double> partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const + { + const auto total_path_duration = total_length / partition_speed; + if (partition_duration >= total_path_duration) + { + const auto remaining_partition_duration = partition_duration - total_path_duration; + const GCodePath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; + return std::make_tuple(gcode_path, std::nullopt, remaining_partition_duration); + } + + 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]; + + 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 auto segment_duration = segment_length / partition_speed; + + if (current_partition_duration + segment_duration < partition_duration) + { + prev_point = next_point; + current_partition_duration += segment_duration; + partition_index += iteration_direction; + } + else + { + 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); + + /* + * partition point + * v + * 0---------1---------2----x------3---------4 + * ^ ^ + * partition index when partition_index when + * going forwards going backwards + * + * When we partition the path in a "left" and "right" path we + * expect we end up with the same path for the same partition + * if we go forwards or backwards. This is why + * partition_point_index = partition_index + 1 + * when going _forwards_, while going _backwards_ it is equal + * to + * partition_point_index = partition_index + * + * Given this new index every point for which + * 0 >= i > partition_point_index + * holds belongs to the _left_ path while every point for which + * partition_point_index >= i > points.size() + * belongs to the right path. + */ + const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; + + // points left of the partition_index + geometry::polyline<> left_points; + for (unsigned int i = 0; i < partition_point_index; ++i) + { + left_points.emplace_back(points[i]); + } + left_points.emplace_back(partition_point); + + // points right of the partition_index + geometry::polyline<> right_points; + right_points.emplace_back(partition_point); + for (unsigned int i = partition_point_index; i < points.size(); ++i) + { + right_points.emplace_back(points[i]); + } + + switch (direction) + { + case utils::Direction::Forward: + { + const GCodePath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = left_points, + .speed = partition_speed, + }; + const GCodePath remaining_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = right_points, + .speed = speed, + }; + return std::make_tuple(partition_gcode_path, remaining_gcode_path, .0); + }; + case utils::Direction::Backward: + { + const GCodePath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = right_points, + .speed = partition_speed, + }; + const GCodePath remaining_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = left_points, + .speed = speed, + }; + return std::make_tuple(partition_gcode_path, remaining_gcode_path, .0); + } + } + } + } + } + + cura::GCodePath toClassicPath(const bool include_first_point) const + { + cura::GCodePath output_path = *original_gcode_path_data; + + output_path.points.clear(); + for (auto& point : points | ranges::views::drop(include_first_point ? 0 : 1)) + { + output_path.points.push_back(point); + } + + output_path.config.speed_derivatives.speed = speed * 1e-3; + + return output_path; + } +}; + +struct GCodeState +{ + double current_flow{ 0.0 }; // um^3/s + double flow_acceleration{ 0.0 }; // um^3/s^2 + double flow_deceleration{ 0.0 }; // um^3/s^2 + double discretized_duration{ 0.0 }; // s + double discretized_duration_remaining{ 0.0 }; // s + double target_end_flow{ 0.0 }; // um^3/s + double reset_flow_duration{ 0.0 }; // s + FlowState flow_state{ FlowState::UNDEFINED }; + + std::vector processGcodePaths(const std::vector& gcode_paths) + { + // reset the discretized_duration_remaining + discretized_duration_remaining = 0; + + std::vector forward_pass_gcode_paths; + for (auto& gcode_path : gcode_paths) + { + auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Forward); + for (auto& path : discretized_paths) + { + forward_pass_gcode_paths.emplace_back(path); + } + } + + // reset the discretized_duration_remaining + 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 + // 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); + + std::list backward_pass_gcode_paths; + for (auto& gcode_path : forward_pass_gcode_paths | ranges::views::reverse) + { + auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Backward); + for (auto& path : discretized_paths) + { + backward_pass_gcode_paths.emplace_front(path); + } + } + + return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); + } + + /* + * Discretizes a GCodePath into multiple GCodePaths with a gradual increase in flow. + * + * @param path the path to discretize + * + * @return a vector of discretized paths with a gradual increase in flow + */ + std::vector processGcodePath(const GCodePath& path, const utils::Direction direction) + { + if (path.isTravel()) + { + if (path.isRetract() || path.totalDuration() > reset_flow_duration) + { + flow_state = FlowState::UNDEFINED; + } + return { path }; + } + + // After a long travel move we want to reset the flow to the target end flow + if (flow_state == FlowState::UNDEFINED && direction == utils::Direction::Forward) + { + current_flow = path.targetFlow(); + } + + auto target_flow = path.flow(); + if (target_flow <= current_flow) + { + current_flow = target_flow; + discretized_duration_remaining = 0; + flow_state = FlowState::STABLE; + return { path }; + } + + const auto extrusion_volume_per_mm = path.extrusionVolumePerMm(); // um^3/um + + std::vector discretized_paths; + + GCodePath remaining_path = path; + + if (discretized_duration_remaining > 0.) + { + const auto discretized_segment_speed = current_flow / extrusion_volume_per_mm; // um^3/s / um^3/um = um/s + const auto [partitioned_gcode_path, new_remaining_path, remaining_partition_duration] + = path.partition(discretized_duration_remaining, discretized_segment_speed, direction); + discretized_duration_remaining = std::max(.0, discretized_duration_remaining - remaining_partition_duration); + if (new_remaining_path.has_value()) + { + remaining_path = new_remaining_path.value(); + discretized_paths.emplace_back(partitioned_gcode_path); + } + else + { + flow_state = FlowState::TRANSITION; + return { partitioned_gcode_path }; + } + } + + // while we have not reached the target flow, iteratively discretize the path + // such that the new path has a duration of discretized_duration and with each + // iteration an increased flow of flow_acceleration + while (current_flow < target_flow) + { + const auto flow_delta = (direction == utils::Forward ? flow_acceleration : flow_deceleration) * discretized_duration; + current_flow = std::min(target_flow, current_flow + flow_delta); + + const auto segment_speed = current_flow / extrusion_volume_per_mm; // um^3/s / um^3/um = um/s + + if (current_flow == target_flow) + { + remaining_path.speed = segment_speed; + discretized_duration_remaining = std::max(discretized_duration_remaining - remaining_path.totalDuration(), .0); + flow_state = discretized_duration_remaining > 0. ? FlowState::TRANSITION : FlowState::STABLE; + discretized_paths.emplace_back(remaining_path); + return discretized_paths; + } + + const auto [partitioned_gcode_path, new_remaining_path, remaining_partition_duration] = remaining_path.partition(discretized_duration, segment_speed, direction); + + // when we have remaining paths, we should have no remaining duration as the + // remaining duration should then be consumed by the remaining paths + assert(! new_remaining_path.has_value() || remaining_partition_duration == 0); + // having no remaining paths implies that there is a duration remaining that should be consumed + // by the next path + assert(new_remaining_path.has_value() || remaining_partition_duration > 0); + + discretized_paths.emplace_back(partitioned_gcode_path); + + if (new_remaining_path.has_value()) + { + remaining_path = new_remaining_path.value(); + } + else + { + flow_state = FlowState::TRANSITION; + discretized_duration_remaining = remaining_partition_duration; + return discretized_paths; + } + } + discretized_paths.emplace_back(remaining_path); + + flow_state = discretized_duration_remaining > 0. ? FlowState::TRANSITION : FlowState::STABLE; + + return discretized_paths; + } +}; + +} // namespace cura::gradual_flow + +#endif // GRADUAL_FLOW_GCODE_PATH_H diff --git a/include/gradual_flow/point_container.h b/include/gradual_flow/point_container.h new file mode 100644 index 0000000000..ae070eeddc --- /dev/null +++ b/include/gradual_flow/point_container.h @@ -0,0 +1,134 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_POINT_CONTAINER_H +#define GRADUAL_FLOW_POINT_CONTAINER_H + +#include +#include +#include +#include + +#include +#include + +#include "gradual_flow/concepts.h" + +namespace cura::gradual_flow::geometry +{ + +using Point = ClipperLib::IntPoint; + +/*! The base clase of all point based container types + * + * @tparam P + * @tparam IsClosed + * @tparam Direction + * @tparam Container + */ +template +struct point_container : public std::vector

+{ + inline static constexpr bool is_closed = IsClosed; + inline static constexpr direction winding = Direction; + + constexpr point_container() noexcept = default; + constexpr point_container(std::initializer_list

points) noexcept + : std::vector

(points) + { + } +}; + +template +struct polyline : public point_container +{ + constexpr polyline() noexcept = default; + constexpr polyline(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +struct polygon : public point_container +{ + constexpr polygon() noexcept = default; + constexpr polygon(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon(std::initializer_list

) -> polygon; + +template +struct polygon_outer : public point_container +{ + constexpr polygon_outer() noexcept = default; + constexpr polygon_outer(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon_outer(std::initializer_list

) -> polygon_outer

; + +template +struct polygon_inner : public point_container +{ + constexpr polygon_inner() noexcept = default; + constexpr polygon_inner(std::initializer_list

points) noexcept + : point_container(points) + { + } +}; + +template +polygon_inner(std::initializer_list

) -> polygon_inner

; + +template +struct polygons : public std::vector*> +{ + constexpr polygons() noexcept = default; + constexpr polygons(std::initializer_list*> polygons) noexcept + : std::vector*>(polygons) + { + } + + constexpr auto outer() noexcept + { + return polygon_outer{ this->front() }; + } + + constexpr auto inners() noexcept + { + return ranges::views::drop(this->base(), 1) + | ranges::views::transform( + [](auto& p) + { + return polygon_inner{ p }; + }); + } +}; + +template +polygons(polygon_outer

, std::initializer_list>) -> polygons

; + +} // namespace cura::gradual_flow::geometry + +static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0) +{ + return cura::gradual_flow::geometry::Point{ -p0.X, -p0.Y }; +} +static inline cura::gradual_flow::geometry::Point operator+(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) +{ + return cura::gradual_flow::geometry::Point{ p0.X + p1.X, p0.Y + p1.Y }; +} +static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) +{ + return cura::gradual_flow::geometry::Point{ p0.X - p1.X, p0.Y - p1.Y }; +} + +#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/processor.h new file mode 100644 index 0000000000..9c984c03a9 --- /dev/null +++ b/include/gradual_flow/processor.h @@ -0,0 +1,100 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_PROCESSOR_H +#define GRADUAL_FLOW_PROCESSOR_H + +#include "Application.h" +#include "LayerPlan.h" +#include "Scene.h" +#include "gradual_flow/gcode_path.h" + +namespace cura::gradual_flow::Processor +{ +void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) +{ + const Scene& scene = Application::getInstance().current_slice_->scene; + const Settings& extruder_settings = scene.extruders[extruder_nr].settings_; + + if (extruder_settings.get("gradual_flow_enabled")) + { + // Convert the gcode paths to a format that suits our calculations more + std::vector gcode_paths; + + // Process first path + for (const cura::GCodePath& path : extruder_plan_paths | ranges::views::take(1)) + { + geometry::polyline<> points; + for (const Point2LL& point : path.points) + { + points.emplace_back(point); + } + gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + } + + /* Process remaining paths + * We need to add the last point of the previous path to the current path + * since the paths in Cura are a connected line string and a new path begins + * where the previous path ends (see figure below). + * { Path A } { Path B } { ...etc + * a.1-----------a.2------a.3---------a.4------b.1--------b.2--- c.1------- + * For our purposes it is easier that each path is a separate line string, and + * no knowledge of the previous path is needed. + */ + for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) + { + geometry::polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; + for (const Point2LL& point : path.points) + { + points.emplace_back(point); + } + gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + } + + constexpr auto non_zero_flow_view = ranges::views::transform( + [](const auto& path) + { + return path.flow(); + }) + | ranges::views::drop_while( + [](const auto flow) + { + return flow == 0.0; + }); + auto gcode_paths_non_zero_flow_view = gcode_paths | non_zero_flow_view; + + const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration"); + + auto target_flow = ranges::empty(gcode_paths_non_zero_flow_view) ? 0.0 : ranges::front(gcode_paths_non_zero_flow_view); + + GCodeState state{ + .current_flow = target_flow, + .flow_acceleration = flow_limit, + .flow_deceleration = flow_limit, + .discretized_duration = extruder_settings.get("gradual_flow_discretisation_step_size"), + // take the first path's target flow as the target flow, this might + // not be correct, but it is safe to assume the target flow for the + // next layer is the same as the target flow of the current layer + .target_end_flow = target_flow, + .reset_flow_duration = extruder_settings.get("reset_flow_duration"), + }; + + const auto limited_flow_acceleration_paths = state.processGcodePaths(gcode_paths); + // Copy newly generated paths to actual plan + + std::vector new_paths; + for (const auto& [index, gcode_path] : limited_flow_acceleration_paths | ranges::views::enumerate) + { + // since the first point is added from the previous path in the initial conversion, + // we should remove it here again. Note that the first point is added for every path + // except the first one, so we should only remove it if it is not the first path + const auto include_first_point = index == 0; + new_paths.push_back(gcode_path.toClassicPath(include_first_point)); + } + + extruder_plan_paths = new_paths; + } +} +} // namespace cura::gradual_flow::Processor + +#endif // GRADUAL_FLOW_PROCESSOR_H diff --git a/include/gradual_flow/utils.h b/include/gradual_flow/utils.h new file mode 100644 index 0000000000..1bcc21e60c --- /dev/null +++ b/include/gradual_flow/utils.h @@ -0,0 +1,85 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_UTILS_H +#define GRADUAL_FLOW_UTILS_H + + +namespace cura::gradual_flow::utils +{ + +enum Direction +{ + Forward, + Backward, +}; + +/* + * \brief Converts HSV values to RGB values. + * + * \param H Hue value in range [0, 360] + * \param S Saturation value in range [0, 100] + * \param V Value value in range [0, 100] + * + * \return A tuple containing the RGB values in range [0, 255] + */ +std::tuple hsvToRgb(double H, double S, double V) +{ + // Code taken from https://www.codespeedy.com/hsv-to-rgb-in-cpp/ and slightly modified + if (H > 360. || H < 0. || S > 100. || S < 0. || V > 100. || V < 0.) + { + throw std::invalid_argument("The given HSV values are not in valid range"); + } + auto s = S * .01; + auto v = V * .01; + auto C = s * v; + auto X = C * (1. - abs(fmod(H / 60.0, 2.) - 1.)); + auto m = v - C; + auto r = 0., g = 0., b = 0.; + if (H >= 0. && H < 60.) + { + r = C; + g = X; + b = 0.; + } + else if (H >= 60. && H < 120.) + { + r = X; + g = C; + b = 0.; + } + else if (H >= 120. && H < 180.) + { + r = 0.; + g = C; + b = X; + } + else if (H >= 180. && H < 240.) + { + r = 0.; + g = X; + b = C; + } + else if (H >= 240. && H < 300.) + { + r = X; + g = 0.; + b = C; + } + else + { + r = C; + g = 0.; + b = X; + } + + int R = (r + m) * 255.; + int G = (g + m) * 255.; + int B = (b + m) * 255.; + + return std::make_tuple(R, G, B); +} + +} // namespace cura::gradual_flow::utils + +#endif // GRADUAL_FLOW_UTILS_H diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 669bbfd5d8..552f1c9c60 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1270,6 +1270,8 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS } } + gcode_layer.applyGradualFlow(); + gcode_layer.applyModifyPlugin(); time_keeper.registerTime("Modify plugin"); diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index ee0b331396..946d3653af 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -19,6 +19,7 @@ #include "WipeScriptConfig.h" #include "communication/Communication.h" #include "geometry/OpenPolyline.h" +#include "gradual_flow/processor.h" #include "pathPlanning/Comb.h" #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" @@ -645,8 +646,6 @@ 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 -static int i = 0; - void LayerPlan::addWallLine( const Point2LL& p0, const Point2LL& p1, @@ -2632,6 +2631,14 @@ void LayerPlan::applyBackPressureCompensation() } } +void LayerPlan::applyGradualFlow() +{ + for (ExtruderPlan& extruder_plan : extruder_plans_) + { + gradual_flow::Processor::process(extruder_plan.paths_, extruder_plan.extruder_nr_, layer_nr_); + } +} + LayerIndex LayerPlan::getLayerNr() const { return layer_nr_; From 6083f4341c574f97333e74a6cb913aafb54c2e34 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 14:39:57 +0200 Subject: [PATCH 31/60] Fix missing setting value conversion CURA-12096 --- include/gradual_flow/processor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/processor.h index 9c984c03a9..520b1ac015 100644 --- a/include/gradual_flow/processor.h +++ b/include/gradual_flow/processor.h @@ -63,7 +63,7 @@ void process(std::vector& extruder_plan_paths, const size_t ext }); auto gcode_paths_non_zero_flow_view = gcode_paths | non_zero_flow_view; - const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration"); + const auto flow_limit = extruder_settings.get(layer_nr == 0 ? "layer_0_max_flow_acceleration" : "max_flow_acceleration") * 1e9; auto target_flow = ranges::empty(gcode_paths_non_zero_flow_view) ? 0.0 : ranges::front(gcode_paths_non_zero_flow_view); From 38357c4d6a604b495ab26db68c917c98d0ff8c99 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 15:59:25 +0200 Subject: [PATCH 32/60] Rename gradual flow classes and structs for consistency CURA-12096 Also removed the "boost_tags.h" header which seems useless in this context. And also renamed the GCodePath struct defined by gradual flow to FlowLimitedPath so that it is less confusing with the already defined GCodePath. --- .../gradual_flow/{concepts.h => Concepts.h} | 43 +++--- .../{gcode_path.h => FlowLimitedPath.h} | 45 +++--- include/gradual_flow/PointContainer.h | 120 ++++++++++++++++ .../gradual_flow/{processor.h => Processor.h} | 18 +-- include/gradual_flow/{utils.h => Utils.h} | 0 include/gradual_flow/boost_tags.h | 135 ------------------ include/gradual_flow/point_container.h | 134 ----------------- src/LayerPlan.cpp | 2 +- 8 files changed, 175 insertions(+), 322 deletions(-) rename include/gradual_flow/{concepts.h => Concepts.h} (60%) rename include/gradual_flow/{gcode_path.h => FlowLimitedPath.h} (91%) create mode 100644 include/gradual_flow/PointContainer.h rename include/gradual_flow/{processor.h => Processor.h} (85%) rename include/gradual_flow/{utils.h => Utils.h} (100%) delete mode 100644 include/gradual_flow/boost_tags.h delete mode 100644 include/gradual_flow/point_container.h diff --git a/include/gradual_flow/concepts.h b/include/gradual_flow/Concepts.h similarity index 60% rename from include/gradual_flow/concepts.h rename to include/gradual_flow/Concepts.h index 923157ada7..885922911b 100644 --- a/include/gradual_flow/concepts.h +++ b/include/gradual_flow/Concepts.h @@ -18,7 +18,8 @@ namespace cura::gradual_flow { -enum class direction + +enum class Direction { NA, CW, @@ -29,43 +30,43 @@ namespace concepts { template -concept closable = requires(T t) +concept Closable = requires(T t) { requires ranges::convertible_to; }; template -concept is_closed_point_container = closable && requires(T t) +concept IsClosedPointContainer = Closable && requires(T t) { t.is_closed == true; }; template -concept is_open_point_container = closable && requires(T t) +concept IsOpenPointContainer = Closable && requires(T t) { t.is_closed == false; }; template -concept directional = requires(T t) +concept Directional = requires(T t) { - requires std::is_same_v; + requires std::is_same_v; }; template -concept is_clockwise_point_container = directional && requires(T t) +concept IsClockwisePointContainer = Directional && requires(T t) { - t.winding == direction::CW; + t.winding == Direction::CW; }; template -concept is_counterclockwise_point_container = directional && requires(T t) +concept IsCounterclockwisePointContainer = Directional && requires(T t) { - t.winding == direction::CCW; + t.winding == Direction::CCW; }; template -concept point2d_named = requires(T point) +concept Point2DNamed = requires(T point) { point.X; point.Y; @@ -77,10 +78,10 @@ concept point2d_named = requires(T point) * @tparam T Type to check */ template -concept point2d = point2d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); +concept Point2D = Point2DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); template -concept point3d_named = requires(T point) +concept Point3DNamed = requires(T point) { point.x; point.y; @@ -93,10 +94,10 @@ concept point3d_named = requires(T point) * @tparam T Type to check */ template -concept point3d = point3d_named ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); +concept Point3D = Point3DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); template -concept point_named = point2d_named || point3d_named; +concept PointNamed = Point2DNamed || Point3DNamed; /*! * @brief Either a Point2D or a Point3D @@ -104,22 +105,22 @@ concept point_named = point2d_named || point3d_named; * @tparam T Type to check */ template -concept point = point2d || point3d; +concept Point = Point2D || Point3D; template -concept point_ranged = point && ! point2d_named && ! point3d_named; +concept PointRanged = Point && ! Point2DNamed && ! Point3DNamed; template -concept polyline = ranges::range && is_open_point_container && point; +concept Polyline = ranges::range && IsOpenPointContainer && Point; template -concept polygon = ranges::range && is_closed_point_container && point; +concept Polygon = ranges::range && IsClosedPointContainer && Point; template -concept polygons = ranges::range && polygon; +concept Polygons = ranges::range && Polygon; template -concept poly_range = polygon || polyline; +concept PolyRange = Polygon || Polyline; } // namespace concepts } // namespace cura::gradual_flow diff --git a/include/gradual_flow/gcode_path.h b/include/gradual_flow/FlowLimitedPath.h similarity index 91% rename from include/gradual_flow/gcode_path.h rename to include/gradual_flow/FlowLimitedPath.h index 56c278dbf0..688ec7ef1b 100644 --- a/include/gradual_flow/gcode_path.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -13,8 +13,8 @@ #include #include -#include "gradual_flow/point_container.h" -#include "gradual_flow/utils.h" +#include "gradual_flow/PointContainer.h" +#include "gradual_flow/Utils.h" #include "pathPlanning/GCodePath.h" namespace cura::gradual_flow @@ -27,10 +27,10 @@ enum class FlowState UNDEFINED }; -struct GCodePath +struct FlowLimitedPath { - const cura::GCodePath* original_gcode_path_data; - geometry::polyline<> points; + const GCodePath* original_gcode_path_data; + geometry::Polyline<> points; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -165,13 +165,14 @@ struct GCodePath * partitioned path is equal or longer than the duration of the original * path */ - std::tuple, double> partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const + std::tuple, double> + partition(const double partition_duration, const double partition_speed, const utils::Direction direction) const { const auto total_path_duration = total_length / partition_speed; if (partition_duration >= total_path_duration) { const auto remaining_partition_duration = partition_duration - total_path_duration; - const GCodePath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; + const FlowLimitedPath gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = points, .speed = partition_speed }; return std::make_tuple(gcode_path, std::nullopt, remaining_partition_duration); } @@ -226,7 +227,7 @@ struct GCodePath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - geometry::polyline<> left_points; + geometry::Polyline<> left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -234,7 +235,7 @@ struct GCodePath left_points.emplace_back(partition_point); // points right of the partition_index - geometry::polyline<> right_points; + geometry::Polyline<> right_points; right_points.emplace_back(partition_point); for (unsigned int i = partition_point_index; i < points.size(); ++i) { @@ -245,12 +246,12 @@ struct GCodePath { case utils::Direction::Forward: { - const GCodePath partition_gcode_path{ + const FlowLimitedPath partition_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = left_points, .speed = partition_speed, }; - const GCodePath remaining_gcode_path{ + const FlowLimitedPath remaining_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = right_points, .speed = speed, @@ -259,12 +260,12 @@ struct GCodePath }; case utils::Direction::Backward: { - const GCodePath partition_gcode_path{ + const FlowLimitedPath partition_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = right_points, .speed = partition_speed, }; - const GCodePath remaining_gcode_path{ + const FlowLimitedPath remaining_gcode_path{ .original_gcode_path_data = original_gcode_path_data, .points = left_points, .speed = speed, @@ -276,9 +277,9 @@ struct GCodePath } } - cura::GCodePath toClassicPath(const bool include_first_point) const + GCodePath toClassicPath(const bool include_first_point) const { - cura::GCodePath output_path = *original_gcode_path_data; + GCodePath output_path = *original_gcode_path_data; output_path.points.clear(); for (auto& point : points | ranges::views::drop(include_first_point ? 0 : 1)) @@ -303,12 +304,12 @@ struct GCodeState double reset_flow_duration{ 0.0 }; // s FlowState flow_state{ FlowState::UNDEFINED }; - std::vector processGcodePaths(const std::vector& gcode_paths) + std::vector processGcodePaths(const std::vector& gcode_paths) { // reset the discretized_duration_remaining discretized_duration_remaining = 0; - std::vector forward_pass_gcode_paths; + std::vector forward_pass_gcode_paths; for (auto& gcode_path : gcode_paths) { auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Forward); @@ -327,7 +328,7 @@ struct GCodeState // instead. current_flow = std::min(current_flow, target_end_flow); - std::list backward_pass_gcode_paths; + std::list backward_pass_gcode_paths; for (auto& gcode_path : forward_pass_gcode_paths | ranges::views::reverse) { auto discretized_paths = processGcodePath(gcode_path, gradual_flow::utils::Direction::Backward); @@ -337,7 +338,7 @@ struct GCodeState } } - return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); + return std::vector(backward_pass_gcode_paths.begin(), backward_pass_gcode_paths.end()); } /* @@ -347,7 +348,7 @@ struct GCodeState * * @return a vector of discretized paths with a gradual increase in flow */ - std::vector processGcodePath(const GCodePath& path, const utils::Direction direction) + std::vector processGcodePath(const FlowLimitedPath& path, const utils::Direction direction) { if (path.isTravel()) { @@ -375,9 +376,9 @@ struct GCodeState const auto extrusion_volume_per_mm = path.extrusionVolumePerMm(); // um^3/um - std::vector discretized_paths; + std::vector discretized_paths; - GCodePath remaining_path = path; + FlowLimitedPath remaining_path = path; if (discretized_duration_remaining > 0.) { diff --git a/include/gradual_flow/PointContainer.h b/include/gradual_flow/PointContainer.h new file mode 100644 index 0000000000..89602f3d3d --- /dev/null +++ b/include/gradual_flow/PointContainer.h @@ -0,0 +1,120 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef GRADUAL_FLOW_POINT_CONTAINER_H +#define GRADUAL_FLOW_POINT_CONTAINER_H + +#include +#include +#include +#include + +#include +#include + +#include "geometry/Point2LL.h" +#include "gradual_flow/Concepts.h" + +namespace cura::gradual_flow::geometry +{ + +/*! The base clase of all point based container types + * + * @tparam P + * @tparam IsClosed + * @tparam Direction + * @tparam Container + */ +template +struct PointContainer : public std::vector

+{ + inline static constexpr bool is_closed = IsClosed; + inline static constexpr Direction winding = direction; + + constexpr PointContainer() noexcept = default; + constexpr PointContainer(std::initializer_list

points) noexcept + : std::vector

(points) + { + } +}; + +template +struct Polyline : public PointContainer +{ + constexpr Polyline() noexcept = default; + constexpr Polyline(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +struct Polygon : public PointContainer +{ + constexpr Polygon() noexcept = default; + constexpr Polygon(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +Polygon(std::initializer_list

) -> Polygon; + +template +struct PolygonOuter : public PointContainer +{ + constexpr PolygonOuter() noexcept = default; + constexpr PolygonOuter(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +PolygonOuter(std::initializer_list

) -> PolygonOuter

; + +template +struct PolygonInner : public PointContainer +{ + constexpr PolygonInner() noexcept = default; + constexpr PolygonInner(std::initializer_list

points) noexcept + : PointContainer(points) + { + } +}; + +template +PolygonInner(std::initializer_list

) -> PolygonInner

; + +template +struct Polygons : public std::vector*> +{ + constexpr Polygons() noexcept = default; + constexpr Polygons(std::initializer_list*> polygons) noexcept + : std::vector*>(polygons) + { + } + + constexpr auto outer() noexcept + { + return PolygonOuter{ this->front() }; + } + + constexpr auto inners() noexcept + { + return ranges::views::drop(this->base(), 1) + | ranges::views::transform( + [](auto& p) + { + return PolygonInner{ p }; + }); + } +}; + +template +Polygons(PolygonOuter

, std::initializer_list>) -> Polygons

; + +} // namespace cura::gradual_flow::geometry + +#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/processor.h b/include/gradual_flow/Processor.h similarity index 85% rename from include/gradual_flow/processor.h rename to include/gradual_flow/Processor.h index 520b1ac015..c73465a54d 100644 --- a/include/gradual_flow/processor.h +++ b/include/gradual_flow/Processor.h @@ -7,11 +7,11 @@ #include "Application.h" #include "LayerPlan.h" #include "Scene.h" -#include "gradual_flow/gcode_path.h" +#include "gradual_flow/FlowLimitedPath.h" namespace cura::gradual_flow::Processor { -void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) +void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) { const Scene& scene = Application::getInstance().current_slice_->scene; const Settings& extruder_settings = scene.extruders[extruder_nr].settings_; @@ -19,17 +19,17 @@ void process(std::vector& extruder_plan_paths, const size_t ext if (extruder_settings.get("gradual_flow_enabled")) { // Convert the gcode paths to a format that suits our calculations more - std::vector gcode_paths; + std::vector gcode_paths; // Process first path - for (const cura::GCodePath& path : extruder_plan_paths | ranges::views::take(1)) + for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - geometry::polyline<> points; + geometry::Polyline<> points; for (const Point2LL& point : path.points) { points.emplace_back(point); } - gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); } /* Process remaining paths @@ -43,12 +43,12 @@ void process(std::vector& extruder_plan_paths, const size_t ext */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - geometry::polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; + geometry::Polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; for (const Point2LL& point : path.points) { points.emplace_back(point); } - gcode_paths.emplace_back(cura::gradual_flow::GCodePath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); } constexpr auto non_zero_flow_view = ranges::views::transform( @@ -82,7 +82,7 @@ void process(std::vector& extruder_plan_paths, const size_t ext const auto limited_flow_acceleration_paths = state.processGcodePaths(gcode_paths); // Copy newly generated paths to actual plan - std::vector new_paths; + std::vector new_paths; for (const auto& [index, gcode_path] : limited_flow_acceleration_paths | ranges::views::enumerate) { // since the first point is added from the previous path in the initial conversion, diff --git a/include/gradual_flow/utils.h b/include/gradual_flow/Utils.h similarity index 100% rename from include/gradual_flow/utils.h rename to include/gradual_flow/Utils.h diff --git a/include/gradual_flow/boost_tags.h b/include/gradual_flow/boost_tags.h deleted file mode 100644 index 9e2273acd5..0000000000 --- a/include/gradual_flow/boost_tags.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_BOOST_TAGS_H -#define GRADUAL_FLOW_BOOST_TAGS_H - - -#ifndef INFILL_BOOST_TAGS_H -#define INFILL_BOOST_TAGS_H - -#include -#include - -#include -#include -#include -#include -#include - -#include "gradual_flow/concepts.h" -#include "gradual_flow/point_container.h" - -namespace boost::geometry::traits -{ - -template<> -struct tag -{ - using type = point_tag; -}; - -template<> -struct dimension : boost::mpl::int_<2> -{ -}; - -template<> -struct coordinate_type -{ - using type = ClipperLib::cInt; -}; - -template<> -struct coordinate_system -{ - using type = boost::geometry::cs::cartesian; -}; - -template -struct access -{ - static_assert(Index < 2, "Out of range"); - using Point = ClipperLib::IntPoint; - using CoordinateType = typename coordinate_type::type; - constexpr static inline CoordinateType get(Point const& p) noexcept - { - return Index == 0 ? p.X : p.Y; - } - - constexpr static inline void set(Point& p, CoordinateType const& value) noexcept - { - if (Index == 0) - { - p.X = value; - } - else - { - p.Y = value; - } - } -}; - -template<> -struct tag> -{ - using type = linestring_tag; -}; - -template<> -struct point_order> -{ - static const order_selector value = clockwise; -}; - -template<> -struct closure> -{ - static const closure_selector value = open; -}; - -template<> -struct tag> -{ - using type = ring_tag; -}; - -template<> -struct point_order> -{ - static const order_selector value = counterclockwise; -}; - -template<> -struct closure> -{ - static const closure_selector value = open; -}; - -template<> -struct tag> -{ - using type = ring_tag; -}; - -template<> -struct point_order -{ - static const order_selector value = counterclockwise; -}; - -template<> -struct closure -{ - static const closure_selector value = open; -}; - -template<> -struct tag -{ - using type = ring_tag; -}; - -} // namespace boost::geometry::traits - -#endif // GRADUAL_FLOW_BOOST_TAGS_H diff --git a/include/gradual_flow/point_container.h b/include/gradual_flow/point_container.h deleted file mode 100644 index ae070eeddc..0000000000 --- a/include/gradual_flow/point_container.h +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_POINT_CONTAINER_H -#define GRADUAL_FLOW_POINT_CONTAINER_H - -#include -#include -#include -#include - -#include -#include - -#include "gradual_flow/concepts.h" - -namespace cura::gradual_flow::geometry -{ - -using Point = ClipperLib::IntPoint; - -/*! The base clase of all point based container types - * - * @tparam P - * @tparam IsClosed - * @tparam Direction - * @tparam Container - */ -template -struct point_container : public std::vector

-{ - inline static constexpr bool is_closed = IsClosed; - inline static constexpr direction winding = Direction; - - constexpr point_container() noexcept = default; - constexpr point_container(std::initializer_list

points) noexcept - : std::vector

(points) - { - } -}; - -template -struct polyline : public point_container -{ - constexpr polyline() noexcept = default; - constexpr polyline(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -struct polygon : public point_container -{ - constexpr polygon() noexcept = default; - constexpr polygon(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon(std::initializer_list

) -> polygon; - -template -struct polygon_outer : public point_container -{ - constexpr polygon_outer() noexcept = default; - constexpr polygon_outer(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon_outer(std::initializer_list

) -> polygon_outer

; - -template -struct polygon_inner : public point_container -{ - constexpr polygon_inner() noexcept = default; - constexpr polygon_inner(std::initializer_list

points) noexcept - : point_container(points) - { - } -}; - -template -polygon_inner(std::initializer_list

) -> polygon_inner

; - -template -struct polygons : public std::vector*> -{ - constexpr polygons() noexcept = default; - constexpr polygons(std::initializer_list*> polygons) noexcept - : std::vector*>(polygons) - { - } - - constexpr auto outer() noexcept - { - return polygon_outer{ this->front() }; - } - - constexpr auto inners() noexcept - { - return ranges::views::drop(this->base(), 1) - | ranges::views::transform( - [](auto& p) - { - return polygon_inner{ p }; - }); - } -}; - -template -polygons(polygon_outer

, std::initializer_list>) -> polygons

; - -} // namespace cura::gradual_flow::geometry - -static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0) -{ - return cura::gradual_flow::geometry::Point{ -p0.X, -p0.Y }; -} -static inline cura::gradual_flow::geometry::Point operator+(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) -{ - return cura::gradual_flow::geometry::Point{ p0.X + p1.X, p0.Y + p1.Y }; -} -static inline cura::gradual_flow::geometry::Point operator-(const cura::gradual_flow::geometry::Point& p0, const cura::gradual_flow::geometry::Point& p1) -{ - return cura::gradual_flow::geometry::Point{ p0.X - p1.X, p0.Y - p1.Y }; -} - -#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 946d3653af..ce654d76ec 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -19,7 +19,7 @@ #include "WipeScriptConfig.h" #include "communication/Communication.h" #include "geometry/OpenPolyline.h" -#include "gradual_flow/processor.h" +#include "gradual_flow/Processor.h" #include "pathPlanning/Comb.h" #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" From 4eb7b1e74e149dd41023183370a4f0421b643ca9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 16:14:42 +0200 Subject: [PATCH 33/60] Add documentation on main methods CURA-12096 --- include/LayerPlan.h | 4 ++++ include/gradual_flow/Processor.h | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 7833e0c8e9..952d7b4921 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -778,6 +778,10 @@ class LayerPlan : public NoCopy */ void applyBackPressureCompensation(); + /*! + * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed + * with a bowden extruder. + */ void applyGradualFlow(); private: diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index c73465a54d..1a5ad9cc19 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -11,6 +11,14 @@ namespace cura::gradual_flow::Processor { + +/*! + * \brief Processes the gradual flow acceleration splitting + * \param extruder_plan_paths The paths of the extruder plan to be processed. I gradual flow is enabled, they will be + * completely rewritten, very likely with a different amout of output paths. + * \param extruder_nr The used extruder number + * \param layer_nr The current layer number + */ void process(std::vector& extruder_plan_paths, const size_t extruder_nr, const size_t layer_nr) { const Scene& scene = Application::getInstance().current_slice_->scene; From f0c0d199edcd575da069b755e40b1c1b9d1f2b84 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 16:41:58 +0200 Subject: [PATCH 34/60] Refine comment CURA-12096 --- include/LayerPlan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 952d7b4921..0e26b35e66 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -779,8 +779,8 @@ class LayerPlan : public NoCopy void applyBackPressureCompensation(); /*! - * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed - * with a bowden extruder. + * If enabled, applies the gradual flow acceleration splitting, that improves printing quality when printing at very high speed, + * especially with a bowden extruder. */ void applyGradualFlow(); From ef4426e8612f456ce84dc1958b02ac6cc7e4af51 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Aug 2024 17:02:15 +0200 Subject: [PATCH 35/60] Use native geometry classes CURA-12096 --- include/geometry/LinesSet.h | 2 +- include/geometry/PointsSet.h | 6 ++ include/gradual_flow/Concepts.h | 128 ------------------------- include/gradual_flow/FlowLimitedPath.h | 8 +- include/gradual_flow/PointContainer.h | 120 ----------------------- include/gradual_flow/Processor.h | 14 +-- 6 files changed, 14 insertions(+), 264 deletions(-) delete mode 100644 include/gradual_flow/Concepts.h delete mode 100644 include/gradual_flow/PointContainer.h diff --git a/include/geometry/LinesSet.h b/include/geometry/LinesSet.h index 7834b821eb..11e5318dcc 100644 --- a/include/geometry/LinesSet.h +++ b/include/geometry/LinesSet.h @@ -158,7 +158,7 @@ class LinesSet template void push_back(LinesSet&& lines_set); - /*! \brief Pushes an entier set at the end */ + /*! \brief Pushes an entire set at the end */ void push_back(const LinesSet& other) { lines_.insert(lines_.end(), other.lines_.begin(), other.lines_.end()); diff --git a/include/geometry/PointsSet.h b/include/geometry/PointsSet.h index f61329929d..7292de13b1 100644 --- a/include/geometry/PointsSet.h +++ b/include/geometry/PointsSet.h @@ -78,6 +78,12 @@ class PointsSet points_.push_back(point); } + /*! \brief Pushes an entire set at the end */ + void push_back(const PointsSet& other) + { + points_.insert(points_.end(), other.points_.begin(), other.points_.end()); + } + void emplace_back(auto&&... args) { points_.emplace_back(std::forward(args)...); diff --git a/include/gradual_flow/Concepts.h b/include/gradual_flow/Concepts.h deleted file mode 100644 index 885922911b..0000000000 --- a/include/gradual_flow/Concepts.h +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_CONCEPTS_H -#define GRADUAL_FLOW_CONCEPTS_H - -#include - -#if __has_include() -#include -#elif __has_include() -#include -#define USE_EXPERIMENTAL_CONCEPTS -#endif - -#include -#include - -namespace cura::gradual_flow -{ - -enum class Direction -{ - NA, - CW, - CCW -}; - -namespace concepts -{ - -template -concept Closable = requires(T t) -{ - requires ranges::convertible_to; -}; - -template -concept IsClosedPointContainer = Closable && requires(T t) -{ - t.is_closed == true; -}; - -template -concept IsOpenPointContainer = Closable && requires(T t) -{ - t.is_closed == false; -}; - -template -concept Directional = requires(T t) -{ - requires std::is_same_v; -}; - -template -concept IsClockwisePointContainer = Directional && requires(T t) -{ - t.winding == Direction::CW; -}; - -template -concept IsCounterclockwisePointContainer = Directional && requires(T t) -{ - t.winding == Direction::CCW; -}; - -template -concept Point2DNamed = requires(T point) -{ - point.X; - point.Y; -}; - -/*! - * @brief A 2D point, defined either as a named object with X and Y attributes, or as a range of two integral values. - * @details This concept is used to check if a type is a 2D point. A 2D point is a type that has a X and Y member or a type that is a range of integral types with a size of 2. - * @tparam T Type to check - */ -template -concept Point2D = Point2DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 2); - -template -concept Point3DNamed = requires(T point) -{ - point.x; - point.y; - point.z; -}; - -/*! - * @brief A 3D point, defined either as a named object with x, y, and z attributes, or as a range of three integral values. - * @details This concept is used to check if a type is a 3D point. A 3D point is a type that has a x, y and z member or a type that is a range of integral types with a size of 3. - * @tparam T Type to check - */ -template -concept Point3D = Point3DNamed ||(ranges::range&& ranges::integral&& std::tuple_size_v == 3); - -template -concept PointNamed = Point2DNamed || Point3DNamed; - -/*! - * @brief Either a Point2D or a Point3D - * @details This concept is used to check if a type is a point. A point is a type that is a 2D or 3D point. - * @tparam T Type to check - */ -template -concept Point = Point2D || Point3D; - -template -concept PointRanged = Point && ! Point2DNamed && ! Point3DNamed; - -template -concept Polyline = ranges::range && IsOpenPointContainer && Point; - -template -concept Polygon = ranges::range && IsClosedPointContainer && Point; - -template -concept Polygons = ranges::range && Polygon; - -template -concept PolyRange = Polygon || Polyline; - -} // namespace concepts -} // namespace cura::gradual_flow - -#endif // GRADUAL_FLOW_CONCEPTS_H diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index 688ec7ef1b..0e3eab859d 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -13,7 +13,7 @@ #include #include -#include "gradual_flow/PointContainer.h" +#include "geometry/PointsSet.h" #include "gradual_flow/Utils.h" #include "pathPlanning/GCodePath.h" @@ -30,7 +30,7 @@ enum class FlowState struct FlowLimitedPath { const GCodePath* original_gcode_path_data; - geometry::Polyline<> points; + PointsSet points; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -227,7 +227,7 @@ struct FlowLimitedPath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - geometry::Polyline<> left_points; + PointsSet left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -235,7 +235,7 @@ struct FlowLimitedPath left_points.emplace_back(partition_point); // points right of the partition_index - geometry::Polyline<> right_points; + PointsSet 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/PointContainer.h b/include/gradual_flow/PointContainer.h deleted file mode 100644 index 89602f3d3d..0000000000 --- a/include/gradual_flow/PointContainer.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef GRADUAL_FLOW_POINT_CONTAINER_H -#define GRADUAL_FLOW_POINT_CONTAINER_H - -#include -#include -#include -#include - -#include -#include - -#include "geometry/Point2LL.h" -#include "gradual_flow/Concepts.h" - -namespace cura::gradual_flow::geometry -{ - -/*! The base clase of all point based container types - * - * @tparam P - * @tparam IsClosed - * @tparam Direction - * @tparam Container - */ -template -struct PointContainer : public std::vector

-{ - inline static constexpr bool is_closed = IsClosed; - inline static constexpr Direction winding = direction; - - constexpr PointContainer() noexcept = default; - constexpr PointContainer(std::initializer_list

points) noexcept - : std::vector

(points) - { - } -}; - -template -struct Polyline : public PointContainer -{ - constexpr Polyline() noexcept = default; - constexpr Polyline(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -struct Polygon : public PointContainer -{ - constexpr Polygon() noexcept = default; - constexpr Polygon(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -Polygon(std::initializer_list

) -> Polygon; - -template -struct PolygonOuter : public PointContainer -{ - constexpr PolygonOuter() noexcept = default; - constexpr PolygonOuter(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -PolygonOuter(std::initializer_list

) -> PolygonOuter

; - -template -struct PolygonInner : public PointContainer -{ - constexpr PolygonInner() noexcept = default; - constexpr PolygonInner(std::initializer_list

points) noexcept - : PointContainer(points) - { - } -}; - -template -PolygonInner(std::initializer_list

) -> PolygonInner

; - -template -struct Polygons : public std::vector*> -{ - constexpr Polygons() noexcept = default; - constexpr Polygons(std::initializer_list*> polygons) noexcept - : std::vector*>(polygons) - { - } - - constexpr auto outer() noexcept - { - return PolygonOuter{ this->front() }; - } - - constexpr auto inners() noexcept - { - return ranges::views::drop(this->base(), 1) - | ranges::views::transform( - [](auto& p) - { - return PolygonInner{ p }; - }); - } -}; - -template -Polygons(PolygonOuter

, std::initializer_list>) -> Polygons

; - -} // namespace cura::gradual_flow::geometry - -#endif // GRADUAL_FLOW_POINT_CONTAINER_H diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index 1a5ad9cc19..c9615d6ba2 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -32,12 +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)) { - geometry::Polyline<> points; - for (const Point2LL& point : path.points) - { - points.emplace_back(point); - } - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = PointsSet(path.points) }); } /* Process remaining paths @@ -51,11 +46,8 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - geometry::Polyline<> points{ ranges::back(ranges::back(gcode_paths).points) }; - for (const Point2LL& point : path.points) - { - points.emplace_back(point); - } + 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 }); } From 6697713ce16a799646e093fb10866eb70684f2d6 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Tue, 20 Aug 2024 15:50:29 +0200 Subject: [PATCH 36/60] build: add bigint flag to cmake for emscripten --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b6cbff243..1bd6d1a6c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,7 +283,7 @@ if (CMAKE_CXX_PLATFORM_ID STREQUAL "emscripten") "SHELL:-sSINGLE_FILE=1" "SHELL:-sENVIRONMENT=web" "SHELL:-sERROR_ON_UNDEFINED_SYMBOLS=0" -# "SHELL:-sWASM_BIGINT=1" + "SHELL:-sWASM_BIGINT=1" "SHELL:-sSTACK_SIZE=196608" $<$:SHELL:-sASSERTIONS=2> $<$:SHELL:-sSAFE_HEAP=1> From 0f8e04fffb27200beaac2a53bcda8185fcb8d619 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Tue, 20 Aug 2024 16:28:20 +0200 Subject: [PATCH 37/60] refactor(FffGcodeWriter): fix extruder order calculation bug The return type of getTotalExtraLayer function on Raft is casted to the value_type to prevent underflow. This previously resulted in method xl to generate invalid gcode. --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index a10a07f425..79bd5087f6 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1536,7 +1536,7 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor extruder_order_per_layer.init(true, storage.print_layer_count); const std::vector extruders_used = storage.getExtrudersUsed(); - for (LayerIndex layer_nr = -Raft::getTotalExtraLayers(); layer_nr < static_cast(storage.print_layer_count); layer_nr++) + for (LayerIndex layer_nr = -static_cast(Raft::getTotalExtraLayers()); layer_nr < static_cast(storage.print_layer_count); layer_nr++) { std::vector extruder_order = getUsedExtrudersOnLayer(storage, last_extruder, layer_nr, extruders_used); extruder_order_per_layer.push_back(extruder_order); From 394b1316736cea4a87326aa01b9bd929fc2a3eb4 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Wed, 21 Aug 2024 09:42:19 +0200 Subject: [PATCH 38/60] refactor(math.h): improve and fix math utility functions --- include/utils/math.h | 105 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/include/utils/math.h b/include/utils/math.h index bb911f67b7..75910b3b7f 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -13,38 +13,125 @@ namespace cura { +/** + * @brief Returns the square of a value. + * + * @tparam T A multipliable type (arithmetic types such as int, float, double, etc.) + * @param a The value to be squared. + * @return T The square of the input value. + */ template [[nodiscard]] T square(const T& a) { return a * a; } -[[nodiscard]] inline int64_t round_divide_signed(const int64_t dividend, const int64_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer +/** + * @brief Returns the quotient of the division of two signed integers, rounded to the nearest integer. + * + * @param dividend The numerator. + * @param divisor The denominator (must not be zero). + * @return int64_t The result of the division rounded to the nearest integer. + * @throws std::invalid_argument If the divisor is zero. + */ +[[nodiscard]] inline int64_t round_divide_signed(const int64_t dividend, const int64_t divisor) { - if ((dividend < 0) ^ (divisor < 0)) // Either the numerator or the denominator is negative, so the result must be negative. + if (divisor == 0) { - return (dividend - divisor / 2) / divisor; // Flip the .5 offset to do proper rounding in the negatives too. + throw std::invalid_argument("Divisor cannot be zero"); + } + + if ((dividend < 0) ^ (divisor < 0)) + { + return (dividend - divisor / 2) / divisor; } return (dividend + divisor / 2) / divisor; } -[[nodiscard]] inline uint64_t ceil_divide_signed(const int64_t dividend, const int64_t divisor) //!< Return dividend divided by divisor rounded up towards positive infinity. +/** + * @brief Returns the quotient of the division of two signed integers, rounded up towards positive infinity. + * + * @param dividend The numerator. + * @param divisor The denominator (must not be zero). + * @return int64_t The result of the division rounded up. + * @throws std::invalid_argument If the divisor is zero. + */ +[[nodiscard]] inline int64_t ceil_divide_signed(const int64_t dividend, const int64_t divisor) { - return static_cast((dividend / divisor) + (dividend * divisor > 0 ? 1 : 0)); + if (divisor == 0) + { + throw std::invalid_argument("Divisor cannot be zero"); + } + + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + // Round up if there's a remainder and the signs of dividend and divisor are the same + if (remainder != 0 && ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0))) + { + quotient += 1; + } + + return quotient; } -[[nodiscard]] inline uint64_t floor_divide_signed(const int64_t dividend, const int64_t divisor) //!< Return dividend divided by divisor rounded down towards negative infinity. +/** + * @brief Returns the quotient of the division of two signed integers, rounded down towards negative infinity. + * + * @param dividend The numerator. + * @param divisor The denominator (must not be zero). + * @return int64_t The result of the division rounded down. + * @throws std::invalid_argument If the divisor is zero. + */ +[[nodiscard]] inline int64_t floor_divide_signed(const int64_t dividend, const int64_t divisor) { - return static_cast((dividend / divisor) + (dividend * divisor > 0 ? 0 : -1)); + if (divisor == 0) + { + throw std::invalid_argument("Divisor cannot be zero"); + } + + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + if (remainder != 0 && ((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0))) + { + quotient -= 1; + } + + return quotient; } -[[nodiscard]] inline uint64_t round_divide(const uint64_t dividend, const uint64_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer +/** + * @brief Returns the quotient of the division of two unsigned integers, rounded to the nearest integer. + * + * @param dividend The numerator. + * @param divisor The denominator (must not be zero). + * @return uint64_t The result of the division rounded to the nearest integer. + */ +[[nodiscard]] inline uint64_t round_divide(const uint64_t dividend, const uint64_t divisor) { + if (divisor == 0) + { + throw std::invalid_argument("Divisor cannot be zero"); + } + return (dividend + divisor / 2) / divisor; } -[[nodiscard]] inline uint64_t round_up_divide(const uint64_t dividend, const uint64_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer +/** + * @brief Returns the quotient of the division of two unsigned integers, rounded up towards positive infinity. + * + * @param dividend The numerator. + * @param divisor The denominator (must not be zero). + * @return uint64_t The result of the division rounded up. + */ +[[nodiscard]] inline uint64_t round_up_divide(const uint64_t dividend, const uint64_t divisor) { + if (divisor == 0) + { + throw std::invalid_argument("Divisor cannot be zero"); + } + return (dividend + divisor - 1) / divisor; } From a58a53a368c32f151bc9c8918e3f8317f5725154 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Wed, 21 Aug 2024 09:52:55 +0200 Subject: [PATCH 39/60] test(math.h): add unit tests --- tests/utils/MathTest.cpp | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/utils/MathTest.cpp diff --git a/tests/utils/MathTest.cpp b/tests/utils/MathTest.cpp new file mode 100644 index 0000000000..7c41384ebd --- /dev/null +++ b/tests/utils/MathTest.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/math.h" + +#include + +#include + +namespace cura +{ +// NOLINTBEGIN(*-magic-numbers) + +// Test cases for the square function +TEST(MathTest, TestSquare) +{ + EXPECT_EQ(square(2), 4) << "Square of 2 should be 4."; + EXPECT_EQ(square(-3), 9) << "Square of -3 should be 9."; + EXPECT_EQ(square(0), 0) << "Square of 0 should be 0."; + EXPECT_EQ(square(1.5), 2.25) << "Square of 1.5 should be 2.25."; +} + +// Test cases for the round_divide_signed function +TEST(MathTest, TestRoundDivideSigned) +{ + EXPECT_EQ(round_divide_signed(10, 3), 3) << "10 / 3 rounded should be 3."; + EXPECT_EQ(round_divide_signed(10, -3), -3) << "10 / -3 rounded should be -3."; + EXPECT_EQ(round_divide_signed(-10, 3), -3) << "-10 / 3 rounded should be -3."; + EXPECT_EQ(round_divide_signed(-10, -3), 3) << "-10 / -3 rounded should be 3."; + + EXPECT_THROW(round_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; +} + +// Test cases for the ceil_divide_signed function +TEST(MathTest, TestCeilDivideSigned) +{ + EXPECT_EQ(ceil_divide_signed(10, 3), 4) << "10 / 3 rounded up should be 4."; + EXPECT_EQ(ceil_divide_signed(10, -3), -3) << "10 / -3 rounded up should be -3."; + EXPECT_EQ(ceil_divide_signed(-10, 3), -3) << "-10 / 3 rounded up should be -3."; + EXPECT_EQ(ceil_divide_signed(-10, -3), 4) << "-10 / -3 rounded up should be 4."; + + EXPECT_THROW(ceil_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; +} + +// Test cases for the floor_divide_signed function +TEST(MathTest, TestFloorDivideSigned) +{ + EXPECT_EQ(floor_divide_signed(10, 3), 3) << "10 / 3 rounded down should be 3."; + EXPECT_EQ(floor_divide_signed(10, -3), -4) << "10 / -3 rounded down should be -4."; + EXPECT_EQ(floor_divide_signed(-10, 3), -4) << "-10 / 3 rounded down should be -4."; + EXPECT_EQ(floor_divide_signed(-10, -3), 3) << "-10 / -3 rounded down should be 3."; + + EXPECT_THROW(floor_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; +} + +// Test cases for the round_divide function +TEST(MathTest, TestRoundDivide) +{ + EXPECT_EQ(round_divide(10, 3), 3) << "10 / 3 rounded should be 3."; + EXPECT_EQ(round_divide(11, 3), 4) << "11 / 3 rounded should be 4."; + EXPECT_EQ(round_divide(9, 3), 3) << "9 / 3 rounded should be 3."; + + EXPECT_THROW(round_divide(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; +} + +// Test cases for the round_up_divide function +TEST(MathTest, TestRoundUpDivide) +{ + EXPECT_EQ(round_up_divide(10, 3), 4) << "10 / 3 rounded up should be 4."; + EXPECT_EQ(round_up_divide(9, 3), 3) << "9 / 3 rounded up should be 3."; + EXPECT_EQ(round_up_divide(1, 1), 1) << "1 / 1 rounded up should be 1."; + + EXPECT_THROW(round_up_divide(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; +} + +// NOLINTEND(*-magic-numbers) + +} // namespace cura From 18cf5865400198006a6650d2f6d9f3fbb56f865f Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 21 Aug 2024 14:30:01 +0200 Subject: [PATCH 40/60] Add GCode prefix handling in Emscripten communication NP-351 --- include/communication/EmscriptenCommunication.h | 6 ++++++ src/communication/CommandLine.cpp | 2 +- src/communication/EmscriptenCommunication.cpp | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index fc2a6897df..b93dafb50d 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -21,6 +21,7 @@ class EmscriptenCommunication : public CommandLine { private: std::string progress_handler_; ///< Handler for progress messages. + std::string gcode_prefix_handler_; /// & { slice_info_handler_ = *ranges::next(slice_info_flag); } + if (auto gcode_prefix_flag = ranges::find(arguments_, "--gcode_prefix_cb"); gcode_prefix_flag != arguments_.end()) + { + gcode_prefix_handler_ = *ranges::next(gcode_prefix_flag); + } +} + +void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", gcode_prefix_handler_ , prefix).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const From ee99fda4ce0a72753e906ae99fd7a3125be0d973 Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Wed, 21 Aug 2024 12:30:44 +0000 Subject: [PATCH 41/60] Applied clang-format. --- src/communication/EmscriptenCommunication.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 00199b4532..25d0a31448 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -39,8 +39,9 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& } } -void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { - emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", gcode_prefix_handler_ , prefix).c_str()); +void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const +{ + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", gcode_prefix_handler_, prefix).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const From 77db867f87b8c6b1d5e8e6d78e582e53c6ca0489 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 21 Aug 2024 14:59:19 +0200 Subject: [PATCH 42/60] fixing typo in .h file NP-351 --- 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 b93dafb50d..d6d07baed3 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -46,7 +46,7 @@ class EmscriptenCommunication : public CommandLine /** * \brief Sends GcodeHeader */ - void ::sendGCodePrefix(const std::string& prefix) const override; + void sendGCodePrefix(const std::string& prefix) const override; /** * \brief Initiates the slicing of the next item. From b45dd95f8edb5f04f2e5f2560d2f0d8de4f655c0 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Wed, 21 Aug 2024 19:30:50 +0200 Subject: [PATCH 43/60] Apply review suggestions Contribute to NP-327 --- src/communication/CommandLine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 51a016c82a..b8d705c4e0 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -192,13 +192,12 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } -#ifdef __EMSCRIPTEN__ else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_prefix_cb")) { + // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; argument = arguments_[argument_index]; } -#endif // __EMSCRIPTEN__ else { spdlog::error("Unknown option: {}", argument); From 714f3e8183ff66370211845e8f70f0b536d28803 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 22 Aug 2024 09:11:16 +0200 Subject: [PATCH 44/60] Fix UT Contribute to NP-327 --- tests/FffGcodeWriterTest.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/FffGcodeWriterTest.cpp b/tests/FffGcodeWriterTest.cpp index 87b20cb64e..a3c52d6509 100644 --- a/tests/FffGcodeWriterTest.cpp +++ b/tests/FffGcodeWriterTest.cpp @@ -54,12 +54,12 @@ class FffGcodeWriterTest : public testing::Test inner_square.back().emplace_back(MM2INT(60), MM2INT(60)); inner_square.back().emplace_back(MM2INT(10), MM2INT(60)); - Application::getInstance().communication_ = new MockCommunication(); + Application::getInstance().communication_ = std::make_shared(); } SliceDataStorage* setUpStorage() { - Application::getInstance().current_slice_ = new Slice(1); + Application::getInstance().current_slice_ = std::make_shared(1); // Define all settings in the mesh group. The extruder train and model settings will fall back on that then. settings = &Application::getInstance().current_slice_->scene.settings; @@ -100,7 +100,7 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) { // SETUP SliceDataStorage* storage = setUpStorage(); - + // Set the fan speed layer time settings (since the LayerPlan constructor copies these). FanSpeedLayerTimeSettings fan_settings; fan_settings.cool_min_layer_time = settings->get("cool_min_layer_time"); @@ -110,7 +110,7 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) fan_settings.cool_fan_speed_max = settings->get("cool_fan_speed_max"); fan_settings.cool_min_speed = settings->get("cool_min_speed"); fan_settings.cool_fan_full_layer = settings->get("cool_fan_full_layer"); - + Mesh mesh(*settings); LayerPlan gcode_layer(*storage, 100, 10000, 100, 0, {fan_settings}, 20, 10, 5000 ); @@ -165,7 +165,7 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) } return false; }; - + // Check the results for (auto poly:inner_square) for (auto point:poly) From 63a84d0640c27345038aae1a8d48e7b9c0a88edc Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 22 Aug 2024 09:50:18 +0200 Subject: [PATCH 45/60] Communication isn't sequential Contribute to NP-327 --- include/communication/EmscriptenCommunication.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index d6d07baed3..7f4c68a14a 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -52,9 +52,11 @@ class EmscriptenCommunication : public CommandLine * \brief Initiates the slicing of the next item. */ void sliceNext() override; + + bool isSequential() const { return false; } }; } // namespace cura #endif // __EMSCRIPTEN__ -#endif // EMSCRIPTENCOMMUNICATION_H \ No newline at end of file +#endif // EMSCRIPTENCOMMUNICATION_H From f87791a515b78bdcb2990b251b662925620e9318 Mon Sep 17 00:00:00 2001 From: jellespijker Date: Thu, 22 Aug 2024 07:50:49 +0000 Subject: [PATCH 46/60] Applied clang-format. --- include/communication/EmscriptenCommunication.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 7f4c68a14a..b2ddc5390f 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -53,7 +53,10 @@ class EmscriptenCommunication : public CommandLine */ void sliceNext() override; - bool isSequential() const { return false; } + bool isSequential() const + { + return false; + } }; } // namespace cura From d5afe4fd954e784b01730a015c994a95f1a48aa8 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 22 Aug 2024 10:41:26 +0200 Subject: [PATCH 47/60] Refactor GCode handler variable and command line flag names Renamed `gcode_prefix_handler_` to `gcode_header_handler_` for clarity. Updated corresponding usage in `EmscriptenCommunication.h`, `CommandLine.cpp`, and `EmscriptenCommunication.cpp`. Contribute to NP-327 --- include/communication/EmscriptenCommunication.h | 2 +- src/communication/CommandLine.cpp | 2 +- src/communication/EmscriptenCommunication.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index b2ddc5390f..28888b5706 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -21,7 +21,7 @@ class EmscriptenCommunication : public CommandLine { private: std::string progress_handler_; ///< Handler for progress messages. - std::string gcode_prefix_handler_; /// & { slice_info_handler_ = *ranges::next(slice_info_flag); } - if (auto gcode_prefix_flag = ranges::find(arguments_, "--gcode_prefix_cb"); gcode_prefix_flag != arguments_.end()) + if (auto gcode_header_flag = ranges::find(arguments_, "--gcode_header_cb"); gcode_header_flag != arguments_.end()) { - gcode_prefix_handler_ = *ranges::next(gcode_prefix_flag); + gcode_header_handler_ = *ranges::next(gcode_header_flag); } } From 38c59c35aaf56a20a8f99c11ddeb0bda57d2a793 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 22 Aug 2024 11:27:42 +0200 Subject: [PATCH 48/60] Fix GCode prefix formatting in EmscriptenCommunication Ensure the prefix argument is correctly formatted as a string in JavaScript function calls. This change prevents potential issues with GCode prefix handling in the communication module as it starts with ';' NP-327 --- 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 d5504add7e..d929a204cb 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -41,7 +41,7 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { - emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", gcode_prefix_handler_, prefix).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_prefix_handler_, prefix).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const From e5b17f9aea941f3e06f894ed1e74482f2b7d6e63 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 22 Aug 2024 11:45:46 +0200 Subject: [PATCH 49/60] Fix GCode prefix handler name in EmscriptenCommunication NP-327 --- 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 d929a204cb..16701a4dea 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -41,7 +41,7 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { - emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_prefix_handler_, prefix).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_header_handler_, prefix).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const From b9d5ef6234435ebd706faab531e6c15177d7a9ed Mon Sep 17 00:00:00 2001 From: Daniel M Date: Thu, 22 Aug 2024 11:53:20 +0200 Subject: [PATCH 50/60] refactor: remove unnecessary type cast --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 46c70be640..c258d9aa3b 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1538,7 +1538,7 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor extruder_order_per_layer.init(true, storage.print_layer_count); const std::vector extruders_used = storage.getExtrudersUsed(); - for (LayerIndex layer_nr = -static_cast(Raft::getTotalExtraLayers()); layer_nr < static_cast(storage.print_layer_count); layer_nr++) + for (LayerIndex layer_nr = -Raft::getTotalExtraLayers(); layer_nr < static_cast(storage.print_layer_count); layer_nr++) { std::vector extruder_order = getUsedExtrudersOnLayer(storage, last_extruder, layer_nr, extruders_used); extruder_order_per_layer.push_back(extruder_order); From 5c564a39dc9dd76be676c62c193ba253c52798af Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 22 Aug 2024 15:32:45 +0200 Subject: [PATCH 51/60] Add base64 encoding for GCode prefix handling. Implemented a `convertTobase64` function to encode strings to base64, ensuring they can be safely transmitted in JavaScript. Updated `EmscriptenCommunication::sendGCodePrefix` to use this new encoding method and modified relevant tests accordingly to check this functionality. NP-327 --- include/utils/string.h | 28 +++++++++++++++++++ src/communication/EmscriptenCommunication.cpp | 4 +-- tests/arcus/ArcusCommunicationTest.cpp | 8 ++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/include/utils/string.h b/include/utils/string.h index 7468077279..1b48dec225 100644 --- a/include/utils/string.h +++ b/include/utils/string.h @@ -8,6 +8,9 @@ #include // sprintf #include #include // ostringstream +#include +#include +#include #include @@ -27,6 +30,31 @@ static inline int stringcasecompare(const char* a, const char* b) return *a - *b; } +// Convert string to base64 string. +// This function is useful to forward string through javascript even if they contain any special strings +// +[[maybe_unused]] static std::string convertTobase64(const std::string& input ) +{ + using namespace boost::archive::iterators; + // prepare the stream to hold the encoded data + std::stringstream output; + + // encode data + typedef base64_from_binary> base64_enc; + std::copy(base64_enc(input.begin()), base64_enc(input.end()), + ostream_iterator(output)); + + // Retrieve the encoded string + std::string output_encoded = output.str(); + + // ensure padding if needed + size_t num = (3 - input.length() % 3) % 3; + for(size_t i = 0; i < num; i++) + { + output_encoded.push_back('='); + } + return output_encoded; +} /*! * Efficient conversion of micron integer type to millimeter string. * diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 16701a4dea..006bb6c523 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -13,7 +13,7 @@ #include #include #include - +#include "utils/string.h" #include "Application.h" #include "FffProcessor.h" #include "Slice.h" @@ -41,7 +41,7 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { - emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_header_handler_, prefix).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_prefix_handler_, convertTobase64(prefix)).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const diff --git a/tests/arcus/ArcusCommunicationTest.cpp b/tests/arcus/ArcusCommunicationTest.cpp index a05b88d98d..3c0eb20d37 100644 --- a/tests/arcus/ArcusCommunicationTest.cpp +++ b/tests/arcus/ArcusCommunicationTest.cpp @@ -14,6 +14,7 @@ #include "geometry/Shape.h" #include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" +#include "utils/string.h" // NOLINTBEGIN(*-magic-numbers) namespace cura @@ -123,15 +124,16 @@ TEST_F(ArcusCommunicationTest, HasSlice) TEST_F(ArcusCommunicationTest, SendGCodePrefix) { - const std::string& prefix = "bladibla"; + const std::string prefix = ";bladiblhjvouyvu\n;iuboua"; + const std::string& encoded_prefix = convertTobase64(prefix); - ac->sendGCodePrefix(prefix); + ac->sendGCodePrefix(encoded_prefix); ac->flushGCode(); EXPECT_GT(socket->sent_messages.size(), 0); bool found_prefix = false; for (const auto& message : socket->sent_messages) { - if (message->DebugString().find(prefix) != std::string::npos) + if (message->DebugString().find(encoded_prefix) != std::string::npos) { found_prefix = true; break; From 1177f6ffc6db625af84cebd7c34076c18e2e5a03 Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Thu, 22 Aug 2024 13:33:19 +0000 Subject: [PATCH 52/60] Applied clang-format. --- include/utils/string.h | 11 +++++------ src/communication/EmscriptenCommunication.cpp | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/utils/string.h b/include/utils/string.h index 1b48dec225..f21bd66524 100644 --- a/include/utils/string.h +++ b/include/utils/string.h @@ -8,10 +8,10 @@ #include // sprintf #include #include // ostringstream + #include -#include #include - +#include #include namespace cura @@ -33,7 +33,7 @@ static inline int stringcasecompare(const char* a, const char* b) // Convert string to base64 string. // This function is useful to forward string through javascript even if they contain any special strings // -[[maybe_unused]] static std::string convertTobase64(const std::string& input ) +[[maybe_unused]] static std::string convertTobase64(const std::string& input) { using namespace boost::archive::iterators; // prepare the stream to hold the encoded data @@ -41,15 +41,14 @@ static inline int stringcasecompare(const char* a, const char* b) // encode data typedef base64_from_binary> base64_enc; - std::copy(base64_enc(input.begin()), base64_enc(input.end()), - ostream_iterator(output)); + std::copy(base64_enc(input.begin()), base64_enc(input.end()), ostream_iterator(output)); // Retrieve the encoded string std::string output_encoded = output.str(); // ensure padding if needed size_t num = (3 - input.length() % 3) % 3; - for(size_t i = 0; i < num; i++) + for (size_t i = 0; i < num; i++) { output_encoded.push_back('='); } diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 006bb6c523..315773363a 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -13,10 +13,11 @@ #include #include #include -#include "utils/string.h" + #include "Application.h" #include "FffProcessor.h" #include "Slice.h" +#include "utils/string.h" namespace cura { From bf76f58ed36e712ec0cd06187e1ae934d88fbb0d Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 22 Aug 2024 16:34:34 +0200 Subject: [PATCH 53/60] Rename handler for GCode prefix transmission. NP-327 --- 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 315773363a..8155290ee9 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -42,7 +42,7 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const { - emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_prefix_handler_, convertTobase64(prefix)).c_str()); + emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_header_handler_, convertTobase64(prefix)).c_str()); } void EmscriptenCommunication::sendProgress(double progress) const From a45a60727725f8426cb0ba1d89ba323442a48b9e Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Thu, 22 Aug 2024 18:24:54 +0200 Subject: [PATCH 54/60] Update include/communication/EmscriptenCommunication.h Co-authored-by: Erwan MATHIEU --- 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 28888b5706..5144b96dc6 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -53,7 +53,7 @@ class EmscriptenCommunication : public CommandLine */ void sliceNext() override; - bool isSequential() const + bool isSequential() const override { return false; } From 98cb1052d9dd08b7003ad0050ada44019621f379 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 26 Aug 2024 10:52:47 +0200 Subject: [PATCH 55/60] Refactor to use existing mesh group object assuming that cloud slicer does not create multiple mesh groups, but one. NP-351 --- src/communication/CommandLine.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 05a3691def..80db95519e 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -437,23 +437,19 @@ void CommandLine::sliceNext() for (const auto& [key, values] : model_settings) { const auto& model_name = key; - - cura::MeshGroup mesh_group; for (const auto& [setting_key, setting_value] : values) { - mesh_group.settings.add(setting_key, setting_value); + slice->scene.mesh_groups[mesh_group_index].settings.add(setting_key, setting_value); } - const auto transformation = mesh_group.settings.get("mesh_rotation_matrix"); - const auto extruder_nr = mesh_group.settings.get("extruder_nr"); + const auto transformation = slice->scene.mesh_groups[mesh_group_index].settings.get("mesh_rotation_matrix"); + const auto extruder_nr = slice->scene.mesh_groups[mesh_group_index].settings.get("extruder_nr"); - if (! loadMeshIntoMeshGroup(&mesh_group, model_name.c_str(), transformation, slice->scene.extruders[extruder_nr].settings_)) + if (! loadMeshIntoMeshGroup(&slice->scene.mesh_groups[mesh_group_index], model_name.c_str(), transformation, slice->scene.extruders[extruder_nr].settings_)) { spdlog::error("Failed to load model: {}. (error number {})", model_name, errno); exit(1); } - - slice->scene.mesh_groups.push_back(std::move(mesh_group)); } for (const auto& [key, value] : limit_to_extruder) { From d1eb8444af532b06068e23c7d19757aa4ddbee39 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 28 Aug 2024 09:48:33 +0200 Subject: [PATCH 56/60] Prevent implicit sign casting Solves unexpected behaviour between different architechtures where the sizes of `size_t`, `maxint_t` etc may differ. NP-343 --- src/ConicalOverhang.cpp | 2 +- src/FffGcodeWriter.cpp | 26 +++++++++++++------------- src/FffPolygonGenerator.cpp | 8 ++++---- src/InterlockingGenerator.cpp | 4 ++-- src/slicer.cpp | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ConicalOverhang.cpp b/src/ConicalOverhang.cpp index 699d9697d7..ab4c60873d 100644 --- a/src/ConicalOverhang.cpp +++ b/src/ConicalOverhang.cpp @@ -23,7 +23,7 @@ void ConicalOverhang::apply(Slicer* slicer, const Mesh& mesh) const coord_t layer_thickness = mesh.settings_.get("layer_height"); coord_t max_dist_from_lower_layer = std::llround(tan_angle * static_cast(layer_thickness)); // max dist which can be bridged - for (LayerIndex layer_nr = slicer->layers.size() - 2; static_cast(layer_nr) >= 0; layer_nr--) + for (LayerIndex layer_nr = LayerIndex(slicer->layers.size()) - 2; layer_nr >= 0; layer_nr--) { SlicerLayer& layer = slicer->layers[static_cast(layer_nr)]; SlicerLayer& layer_above = slicer->layers[static_cast(layer_nr) + 1ul]; diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index c258d9aa3b..5a50c04452 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -575,7 +575,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const size_t surface_extruder_nr = mesh_group_settings.get("raft_surface_extruder_nr").extruder_nr_; coord_t z = 0; - const LayerIndex initial_raft_layer_nr = -Raft::getTotalExtraLayers(); + const LayerIndex initial_raft_layer_nr = -LayerIndex(Raft::getTotalExtraLayers()); const Settings& interface_settings = mesh_group_settings.get("raft_interface_extruder_nr").settings_; const size_t num_interface_layers = interface_settings.get("raft_interface_layers"); const Settings& surface_settings = mesh_group_settings.get("raft_surface_extruder_nr").settings_; @@ -764,7 +764,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) z += raft_interface_z_offset; - for (LayerIndex raft_interface_layer = 1; static_cast(raft_interface_layer) <= num_interface_layers; ++raft_interface_layer) + for (LayerIndex raft_interface_layer = 1; raft_interface_layer <= LayerIndex(num_interface_layers); ++raft_interface_layer) { // raft interface layer const LayerIndex layer_nr = initial_raft_layer_nr + raft_interface_layer; z += interface_layer_height; @@ -925,7 +925,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) z += raft_surface_z_offset; - for (LayerIndex raft_surface_layer = 1; static_cast(raft_surface_layer) <= num_surface_layers; raft_surface_layer++) + for (LayerIndex raft_surface_layer = 1; raft_surface_layer <= LayerIndex(num_surface_layers); raft_surface_layer++) { // raft surface layers const LayerIndex layer_nr = initial_raft_layer_nr + 1 + num_interface_layers + raft_surface_layer - 1; // +1: 1 base layer z += surface_layer_height; @@ -1538,7 +1538,7 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor extruder_order_per_layer.init(true, storage.print_layer_count); const std::vector extruders_used = storage.getExtrudersUsed(); - for (LayerIndex layer_nr = -Raft::getTotalExtraLayers(); layer_nr < static_cast(storage.print_layer_count); layer_nr++) + for (LayerIndex layer_nr = -LayerIndex(Raft::getTotalExtraLayers()); layer_nr < LayerIndex(storage.print_layer_count); layer_nr++) { std::vector extruder_order = getUsedExtrudersOnLayer(storage, last_extruder, layer_nr, extruders_used); extruder_order_per_layer.push_back(extruder_order); @@ -1557,7 +1557,7 @@ void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& stor void FffGcodeWriter::calculatePrimeLayerPerExtruder(const SliceDataStorage& storage) { - LayerIndex first_print_layer = -Raft::getTotalExtraLayers(); + LayerIndex first_print_layer = -LayerIndex(Raft::getTotalExtraLayers()); for (size_t extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; ++extruder_nr) { if (getExtruderNeedPrimeBlobDuringFirstLayer(storage, extruder_nr)) @@ -1567,7 +1567,7 @@ void FffGcodeWriter::calculatePrimeLayerPerExtruder(const SliceDataStorage& stor } } - for (LayerIndex layer_nr = first_print_layer; layer_nr < static_cast(storage.print_layer_count); ++layer_nr) + for (LayerIndex layer_nr = first_print_layer; layer_nr < LayerIndex(storage.print_layer_count); ++layer_nr) { const std::vector used_extruders = storage.getExtrudersUsed(layer_nr); for (size_t extruder_nr = 0; extruder_nr < used_extruders.size(); ++extruder_nr) @@ -1591,7 +1591,7 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayer( assert(static_cast(extruder_count) > 0); std::vector ret; std::vector extruder_is_used_on_this_layer = storage.getExtrudersUsed(layer_nr); - const LayerIndex raft_base_layer_nr = -Raft::getTotalExtraLayers(); + const LayerIndex raft_base_layer_nr = -LayerIndex(Raft::getTotalExtraLayers()); Raft::LayerType layer_type = Raft::getLayerType(layer_nr); if (layer_type == Raft::RaftBase) @@ -1828,7 +1828,7 @@ void FffGcodeWriter::addMeshPartToGCode( // After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. if (added_something - && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < static_cast(mesh.settings.get("initial_bottom_layers")))) + && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < LayerIndex(mesh.settings.get("initial_bottom_layers")))) { coord_t innermost_wall_line_width = mesh.settings.get((mesh.settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); if (gcode_layer.getLayerNr() == 0) @@ -2930,19 +2930,19 @@ bool FffGcodeWriter::processInsets( if (Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize")) { const size_t initial_bottom_layers = mesh.settings.get("initial_bottom_layers"); - const int layer_nr = gcode_layer.getLayerNr(); - if ((layer_nr < static_cast(initial_bottom_layers) + const auto layer_nr = gcode_layer.getLayerNr(); + if ((layer_nr < LayerIndex(initial_bottom_layers) && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths - || (layer_nr >= static_cast(initial_bottom_layers) && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall + || (layer_nr >= LayerIndex(initial_bottom_layers) && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall { // nothing to do return false; } - if (gcode_layer.getLayerNr() >= static_cast(initial_bottom_layers)) + if (gcode_layer.getLayerNr() >= LayerIndex(initial_bottom_layers)) { spiralize = true; } - if (spiralize && gcode_layer.getLayerNr() == static_cast(initial_bottom_layers) + if (spiralize && gcode_layer.getLayerNr() == LayerIndex(initial_bottom_layers) && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_) { // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward added_something = true; diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 10a1336995..0a0351e460 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -557,7 +557,7 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, const siz coord_t surface_line_width = mesh.settings.get("wall_line_width_0"); mesh.layer_nr_max_filled_layer = -1; - for (LayerIndex layer_idx = 0; layer_idx < static_cast(mesh.layers.size()); layer_idx++) + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(mesh.layers.size()); layer_idx++) { SliceLayer& layer = mesh.layers[layer_idx]; @@ -585,7 +585,7 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, const siz break; // all previous meshes have been processed } SliceMeshStorage& other_mesh = *storage.meshes[other_mesh_idx]; - if (layer_idx >= static_cast(other_mesh.layers.size())) + if (layer_idx >= LayerIndex(other_mesh.layers.size())) { // there can be no interaction between the infill mesh and this other non-infill mesh continue; } @@ -875,7 +875,7 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage } for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { - for (LayerIndex layer_nr = static_cast(mesh.layers.size()) - 1; layer_nr > max_print_height_per_extruder[extruder_nr]; layer_nr--) + for (LayerIndex layer_nr = LayerIndex(mesh.layers.size()) - 1; layer_nr > max_print_height_per_extruder[extruder_nr]; layer_nr--) { if (mesh.getExtruderIsUsed(extruder_nr, layer_nr)) { @@ -1097,7 +1097,7 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) return false; }; - for (LayerIndex layer_nr = start_layer_nr; layer_nr < mesh.layers.size(); layer_nr++) + for (LayerIndex layer_nr = start_layer_nr; layer_nr < LayerIndex(mesh.layers.size()); layer_nr++) { SliceLayer& layer = mesh.layers[layer_nr]; for (SliceLayerPart& part : layer.parts) diff --git a/src/InterlockingGenerator.cpp b/src/InterlockingGenerator.cpp index abfab67ae7..92d921e0c4 100644 --- a/src/InterlockingGenerator.cpp +++ b/src/InterlockingGenerator.cpp @@ -222,10 +222,10 @@ void InterlockingGenerator::addBoundaryCells(const std::vector& layers, c std::vector InterlockingGenerator::computeUnionedVolumeRegions() const { - const size_t max_layer_count = std::max(mesh_a_.layers.size(), mesh_b_.layers.size()) + 1; // introduce ghost layer on top for correct skin computation of topmost layer. + const auto max_layer_count = std::max(mesh_a_.layers.size(), mesh_b_.layers.size()) + 1; // introduce ghost layer on top for correct skin computation of topmost layer. std::vector layer_regions(max_layer_count); - for (LayerIndex layer_nr = 0; layer_nr < max_layer_count; layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < LayerIndex(max_layer_count); layer_nr++) { Shape& layer_region = layer_regions[static_cast(layer_nr)]; for (Slicer* mesh : { &mesh_a_, &mesh_b_ }) diff --git a/src/slicer.cpp b/src/slicer.cpp index f66e2d40ec..aa8a4c64e9 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -1017,13 +1017,13 @@ void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::v switch (slicing_tolerance) { case SlicingTolerance::INCLUSIVE: - for (LayerIndex layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) + for (size_t layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) { layers[layer_nr].polygons_ = layers[layer_nr].polygons_.unionPolygons(layers[layer_nr + 1].polygons_); } break; case SlicingTolerance::EXCLUSIVE: - for (LayerIndex layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) + for (size_t layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) { layers[layer_nr].polygons_ = layers[layer_nr].polygons_.intersection(layers[layer_nr + 1].polygons_); } From aa7cbbd849fad50bdd0b1f14e2d48e3cd0ab0854 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 28 Aug 2024 07:49:12 +0000 Subject: [PATCH 57/60] Applied clang-format. --- src/FffGcodeWriter.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 5a50c04452..9ad24e8ec0 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1827,8 +1827,7 @@ void FffGcodeWriter::addMeshPartToGCode( added_something = added_something | processSkin(storage, gcode_layer, mesh, extruder_nr, mesh_config, part); // After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. - if (added_something - && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < LayerIndex(mesh.settings.get("initial_bottom_layers")))) + if (added_something && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < LayerIndex(mesh.settings.get("initial_bottom_layers")))) { coord_t innermost_wall_line_width = mesh.settings.get((mesh.settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); if (gcode_layer.getLayerNr() == 0) @@ -2931,8 +2930,7 @@ bool FffGcodeWriter::processInsets( { const size_t initial_bottom_layers = mesh.settings.get("initial_bottom_layers"); const auto layer_nr = gcode_layer.getLayerNr(); - if ((layer_nr < LayerIndex(initial_bottom_layers) - && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths + if ((layer_nr < LayerIndex(initial_bottom_layers) && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths || (layer_nr >= LayerIndex(initial_bottom_layers) && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall { // nothing to do @@ -2942,8 +2940,7 @@ bool FffGcodeWriter::processInsets( { spiralize = true; } - if (spiralize && gcode_layer.getLayerNr() == LayerIndex(initial_bottom_layers) - && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_) + if (spiralize && gcode_layer.getLayerNr() == LayerIndex(initial_bottom_layers) && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_) { // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward added_something = true; gcode_layer.setIsInside(true); // going to print stuff inside print object From 14630e645174c470026574922104f6d0e8e18c08 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 2 Sep 2024 11:37:32 +0200 Subject: [PATCH 58/60] Cast to `LayerIndex` earlier to prevent multiple LayerIndex casts NP-343 --- src/FffGcodeWriter.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 9ad24e8ec0..4f394d6f36 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2928,25 +2928,25 @@ bool FffGcodeWriter::processInsets( bool spiralize = false; if (Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize")) { - const size_t initial_bottom_layers = mesh.settings.get("initial_bottom_layers"); + const auto initial_bottom_layers = LayerIndex(mesh.settings.get("initial_bottom_layers")); const auto layer_nr = gcode_layer.getLayerNr(); - if ((layer_nr < LayerIndex(initial_bottom_layers) && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths - || (layer_nr >= LayerIndex(initial_bottom_layers) && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall + if ((layer_nr < initial_bottom_layers && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths + || (layer_nr >= initial_bottom_layers && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall { // nothing to do return false; } - if (gcode_layer.getLayerNr() >= LayerIndex(initial_bottom_layers)) + if (gcode_layer.getLayerNr() >= initial_bottom_layers) { spiralize = true; } - if (spiralize && gcode_layer.getLayerNr() == LayerIndex(initial_bottom_layers) && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_) + if (spiralize && gcode_layer.getLayerNr() == initial_bottom_layers && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr_) { // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward added_something = true; gcode_layer.setIsInside(true); // going to print stuff inside print object // start this first wall at the same vertex the spiral starts const Polygon& spiral_inset = part.spiral_wall[0]; - const size_t spiral_start_vertex = storage.spiralize_seam_vertex_indices[initial_bottom_layers]; + const size_t spiral_start_vertex = storage.spiralize_seam_vertex_indices[static_cast(initial_bottom_layers.value)]; if (spiral_start_vertex < spiral_inset.size()) { gcode_layer.addTravel(spiral_inset[spiral_start_vertex]); From d487aaa16d4cf7bce10c5d8ba3a83949d4ca02ea Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 4 Sep 2024 16:55:42 +0200 Subject: [PATCH 59/60] Const-correctness and remove exceptions. Throwing exceptions means you rely on the code around that to be set up for that, which it currently is not. If we only want to know where it crashed, then we can just rely on SIGFPE to be signalled on division-by-zero (which is almost the only case where FPE happens anyway). --- include/utils/math.h | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/include/utils/math.h b/include/utils/math.h index 75910b3b7f..f1ad772c72 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -36,11 +36,6 @@ template */ [[nodiscard]] inline int64_t round_divide_signed(const int64_t dividend, const int64_t divisor) { - if (divisor == 0) - { - throw std::invalid_argument("Divisor cannot be zero"); - } - if ((dividend < 0) ^ (divisor < 0)) { return (dividend - divisor / 2) / divisor; @@ -58,11 +53,6 @@ template */ [[nodiscard]] inline int64_t ceil_divide_signed(const int64_t dividend, const int64_t divisor) { - if (divisor == 0) - { - throw std::invalid_argument("Divisor cannot be zero"); - } - int64_t quotient = dividend / divisor; int64_t remainder = dividend % divisor; @@ -85,19 +75,12 @@ template */ [[nodiscard]] inline int64_t floor_divide_signed(const int64_t dividend, const int64_t divisor) { - if (divisor == 0) - { - throw std::invalid_argument("Divisor cannot be zero"); - } - - int64_t quotient = dividend / divisor; - int64_t remainder = dividend % divisor; - + const int64_t quotient = dividend / divisor; + const int64_t remainder = dividend % divisor; if (remainder != 0 && ((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0))) { - quotient -= 1; + return quotient - 1; } - return quotient; } @@ -110,11 +93,6 @@ template */ [[nodiscard]] inline uint64_t round_divide(const uint64_t dividend, const uint64_t divisor) { - if (divisor == 0) - { - throw std::invalid_argument("Divisor cannot be zero"); - } - return (dividend + divisor / 2) / divisor; } @@ -127,11 +105,6 @@ template */ [[nodiscard]] inline uint64_t round_up_divide(const uint64_t dividend, const uint64_t divisor) { - if (divisor == 0) - { - throw std::invalid_argument("Divisor cannot be zero"); - } - return (dividend + divisor - 1) / divisor; } From 953714c469fba494f6e21df85cb77f734560b4d6 Mon Sep 17 00:00:00 2001 From: KoolAyydMAN <85698684+0x5844@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:10:15 +0200 Subject: [PATCH 60/60] test(utils): remove exception throw test for math functions --- tests/utils/MathTest.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/utils/MathTest.cpp b/tests/utils/MathTest.cpp index 7c41384ebd..71c5e8c8f9 100644 --- a/tests/utils/MathTest.cpp +++ b/tests/utils/MathTest.cpp @@ -27,8 +27,6 @@ TEST(MathTest, TestRoundDivideSigned) EXPECT_EQ(round_divide_signed(10, -3), -3) << "10 / -3 rounded should be -3."; EXPECT_EQ(round_divide_signed(-10, 3), -3) << "-10 / 3 rounded should be -3."; EXPECT_EQ(round_divide_signed(-10, -3), 3) << "-10 / -3 rounded should be 3."; - - EXPECT_THROW(round_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; } // Test cases for the ceil_divide_signed function @@ -38,8 +36,6 @@ TEST(MathTest, TestCeilDivideSigned) EXPECT_EQ(ceil_divide_signed(10, -3), -3) << "10 / -3 rounded up should be -3."; EXPECT_EQ(ceil_divide_signed(-10, 3), -3) << "-10 / 3 rounded up should be -3."; EXPECT_EQ(ceil_divide_signed(-10, -3), 4) << "-10 / -3 rounded up should be 4."; - - EXPECT_THROW(ceil_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; } // Test cases for the floor_divide_signed function @@ -49,8 +45,6 @@ TEST(MathTest, TestFloorDivideSigned) EXPECT_EQ(floor_divide_signed(10, -3), -4) << "10 / -3 rounded down should be -4."; EXPECT_EQ(floor_divide_signed(-10, 3), -4) << "-10 / 3 rounded down should be -4."; EXPECT_EQ(floor_divide_signed(-10, -3), 3) << "-10 / -3 rounded down should be 3."; - - EXPECT_THROW(floor_divide_signed(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; } // Test cases for the round_divide function @@ -60,7 +54,6 @@ TEST(MathTest, TestRoundDivide) EXPECT_EQ(round_divide(11, 3), 4) << "11 / 3 rounded should be 4."; EXPECT_EQ(round_divide(9, 3), 3) << "9 / 3 rounded should be 3."; - EXPECT_THROW(round_divide(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; } // Test cases for the round_up_divide function @@ -69,8 +62,6 @@ TEST(MathTest, TestRoundUpDivide) EXPECT_EQ(round_up_divide(10, 3), 4) << "10 / 3 rounded up should be 4."; EXPECT_EQ(round_up_divide(9, 3), 3) << "9 / 3 rounded up should be 3."; EXPECT_EQ(round_up_divide(1, 1), 1) << "1 / 1 rounded up should be 1."; - - EXPECT_THROW(round_up_divide(10, 0), std::invalid_argument) << "Division by zero should throw an exception."; } // NOLINTEND(*-magic-numbers)