diff --git a/CMakeLists.txt b/CMakeLists.txt index 2287e8f92d..1bd6d1a6c0 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 @@ -270,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/conandata.yml b/conandata.yml index c3ba739591..f5da580fcb 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.8.0" +version: "5.9.0-alpha.0" requirements: - "scripta/0.1.0@ultimaker/testing" requirements_arcus: @@ -6,4 +6,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.2.1" requirements_cura_resources: - - "cura_resources/5.8.0" + - "cura_resources/(latest)@ultimaker/testing" 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") diff --git a/include/Application.h b/include/Application.h index 205766f62f..15a9b94bb2 100644 --- a/include/Application.h +++ b/include/Application.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "utils/NoCopy.h" @@ -38,14 +39,14 @@ class Application : NoCopy * can assume that it is safe to access this without checking whether it is * initialised. */ - Communication* communication_ = nullptr; + std::shared_ptr communication_; /* * \brief The slice that is currently ongoing. * * If no slice has started yet, this will be a nullptr. */ - Slice* current_slice_ = nullptr; + std::shared_ptr current_slice_; /*! * \brief ThreadPool with lifetime tied to Application 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..50195d7b36 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: @@ -778,6 +779,12 @@ class LayerPlan : public NoCopy */ void applyBackPressureCompensation(); + /*! + * 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(); + private: /*! * \brief Compute the preferred or minimum combing boundary diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index 2e3162a71a..f74b9cffe3 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -54,7 +54,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) diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 55c742871a..15286e6049 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 - - std::vector search_directories_; - +protected: /* * \brief The command line arguments that the application was called with. */ std::vector arguments_; +private: + std::vector search_directories_; + /* * The last progress update that we output to stdcerr. */ diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h new file mode 100644 index 0000000000..5144b96dc6 --- /dev/null +++ b/include/communication/EmscriptenCommunication.h @@ -0,0 +1,65 @@ +// 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 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 +{ +private: + std::string progress_handler_; ///< Handler for progress messages. + std::string gcode_header_handler_; ///< Handler for getting the GCode handler. + std::string slice_info_handler_; ///< Handler for slice information messages. + + /** + * \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 Sends GcodeHeader + */ + void sendGCodePrefix(const std::string& prefix) const override; + + /** + * \brief Initiates the slicing of the next item. + */ + void sliceNext() override; + + bool isSequential() const override + { + return false; + } +}; + +} // namespace cura + +#endif // __EMSCRIPTEN__ +#endif // EMSCRIPTENCOMMUNICATION_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/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h new file mode 100644 index 0000000000..0e3eab859d --- /dev/null +++ b/include/gradual_flow/FlowLimitedPath.h @@ -0,0 +1,452 @@ +// 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 "geometry/PointsSet.h" +#include "gradual_flow/Utils.h" +#include "pathPlanning/GCodePath.h" + +namespace cura::gradual_flow +{ + +enum class FlowState +{ + STABLE, + TRANSITION, + UNDEFINED +}; + +struct FlowLimitedPath +{ + const GCodePath* original_gcode_path_data; + PointsSet 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 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); + } + + 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 + PointsSet 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 + PointsSet 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 FlowLimitedPath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = left_points, + .speed = partition_speed, + }; + const FlowLimitedPath 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 FlowLimitedPath partition_gcode_path{ + .original_gcode_path_data = original_gcode_path_data, + .points = right_points, + .speed = partition_speed, + }; + const FlowLimitedPath 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); + } + } + } + } + } + + GCodePath toClassicPath(const bool include_first_point) const + { + 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 FlowLimitedPath& 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; + + FlowLimitedPath 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/Processor.h b/include/gradual_flow/Processor.h new file mode 100644 index 0000000000..c9615d6ba2 --- /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/FlowLimitedPath.h" + +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; + 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 GCodePath& path : extruder_plan_paths | ranges::views::take(1)) + { + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = PointsSet(path.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)) + { + 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 }); + } + + 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") * 1e9; + + 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/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)); 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/include/utils/ExtrusionLine.h b/include/utils/ExtrusionLine.h index 987ce1b783..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 diff --git a/include/utils/math.h b/include/utils/math.h index bb911f67b7..f1ad772c72 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -13,37 +13,97 @@ 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 ((dividend < 0) ^ (divisor < 0)) { - return (dividend - divisor / 2) / divisor; // Flip the .5 offset to do proper rounding in the negatives too. + 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)); + 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)); + const int64_t quotient = dividend / divisor; + const int64_t remainder = dividend % divisor; + if (remainder != 0 && ((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0))) + { + return 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) { 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) { return (dividend + divisor - 1) / divisor; } diff --git a/include/utils/string.h b/include/utils/string.h index 7468077279..f21bd66524 100644 --- a/include/utils/string.h +++ b/include/utils/string.h @@ -9,6 +9,9 @@ #include #include // ostringstream +#include +#include +#include #include namespace cura @@ -27,6 +30,30 @@ 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/Application.cpp b/src/Application.cpp index a2a271f617..319c55e544 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -18,8 +18,10 @@ #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. #include "progress/Progress.h" #include "utils/ThreadPool.h" #include "utils/string.h" //For stringcasecompare. @@ -45,7 +47,6 @@ Application::Application() Application::~Application() { - delete communication_; delete thread_pool_; } @@ -100,7 +101,7 @@ void Application::connect() } } - ArcusCommunication* arcus_communication = new ArcusCommunication(); + auto arcus_communication = std::make_shared(); arcus_communication->connect(ip, port); communication_ = arcus_communication; } @@ -214,8 +215,11 @@ void Application::slice() { arguments.emplace_back(argv_[argument_index]); } - - communication_ = new CommandLine(arguments); +#ifdef __EMSCRIPTEN__ + communication_ = std::make_shared(arguments); +#else + communication_ = std::make_shared(arguments); +#endif } void Application::run(const size_t argc, char** argv) 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 669bbfd5d8..4f394d6f36 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) @@ -574,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_; @@ -763,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; @@ -924,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; @@ -1270,6 +1271,8 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS } } + gcode_layer.applyGradualFlow(); + gcode_layer.applyModifyPlugin(); time_keeper.registerTime("Modify plugin"); @@ -1535,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); @@ -1554,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)) @@ -1564,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) @@ -1588,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) @@ -1824,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() < static_cast(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) @@ -1991,6 +1993,386 @@ 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::INFILL); + + // Select only lines which are needed to support points + for (const auto& line : candidate_lines) + { + for (const auto& point : points) + { + if (LinearAlg2D::getDist2FromLineSegment(line.front(), point, line.back()) <= (line_width / 2) * (line_width / 2)) + { + 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; + + 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 (const auto& angle : candidate_angles) + { + candidate_lines.clear(); + getLinesForArea(candidate_lines, area, angle, points, line_width); + if (candidate_lines.length() < result_lines.length() || result_lines.length() == 0) + { + result_lines = std::move(candidate_lines); + } + } +} + +// 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. + const 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 < EPSILON * EPSILON) // 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) + { + 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) + { + /* 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); + } +} + +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 to be held up. + * A straight line needs support just at the ends. + * A curve needs support at various points along the curve. + * + * 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 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. + */ +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) +{ + // 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; + } + + OpenLinesSet printed_lines_on_layer_above; + for (const SliceLayerPart& part_i : mesh.layers[skin_layer_nr].parts) + { + for (const SkinPart& skin_part : part_i.skin_parts) + { + OpenLinesSet skin_lines; + Shape skin_polygons; + std::vector skin_paths; + + AngleDegrees skin_angle = 45; + if (mesh.skin_angles.size() > 0) + { + 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); + 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 - 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; + 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()); + } + 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); + + // 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()); + + const Shape& inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); + + OpenLinesSet unsupported_line_segments = inv_supported_area.intersection(printed_lines_on_layer_above); + + // This is to work around a rounding issue in the shape library with border points. + const Shape& expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); + + 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 OpenPolyline& a : unsupported_line_segments) + { + 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); + } + } + + for (const auto& pair : map) + { + const Polygon& area = expanded_inv_supported_area[pair.first]; + const PointsSet& points = pair.second; + + OpenLinesSet result_lines; + getBestAngledLinesToSupportPoints(result_lines, Shape(area).offset(infill_line_width / 2 + EPSILON), points, infill_line_width); + + for (const auto& line : part.getOwnInfillArea().intersection(result_lines)) + { + integrateSupportingLine(infill_lines, line); + } + } +} + bool FffGcodeWriter::processSingleLayerInfill( const SliceDataStorage& storage, LayerPlan& gcode_layer, @@ -2236,6 +2618,16 @@ bool FffGcodeWriter::processSingleLayerInfill( } wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. + + 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); + } + const bool walls_generated = std::any_of( wall_tool_paths.cbegin(), wall_tool_paths.cend(), @@ -2536,27 +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 int layer_nr = gcode_layer.getLayerNr(); - if ((layer_nr < static_cast(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 + const auto initial_bottom_layers = LayerIndex(mesh.settings.get("initial_bottom_layers")); + const auto layer_nr = gcode_layer.getLayerNr(); + 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() >= static_cast(initial_bottom_layers)) + if (gcode_layer.getLayerNr() >= initial_bottom_layers) { spiralize = true; } - if (spiralize && gcode_layer.getLayerNr() == static_cast(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]); 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/LayerPlan.cpp b/src/LayerPlan.cpp index 48b0d926e7..fd0f683f7d 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, @@ -1983,7 +1982,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_); @@ -2006,6 +2005,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_); @@ -2544,7 +2552,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); @@ -2666,6 +2674,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_; 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/CommandLine.cpp b/src/communication/CommandLine.cpp index 12e04adc13..80db95519e 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() @@ -146,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; @@ -175,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 (...) { @@ -203,15 +192,12 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } -#ifdef __EMSCRIPTEN__ - else if (argument.find("--progress") == 0) + else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb")) { - // Store progress handler name + // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; argument = arguments_[argument_index]; - progressHandler = argument; } -#endif else { spdlog::error("Unknown option: {}", argument); @@ -266,12 +252,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. @@ -284,13 +270,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': @@ -305,14 +291,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; } @@ -334,7 +320,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 ... */ @@ -432,49 +418,45 @@ 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); } } 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) { 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]; } } @@ -505,11 +487,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 new file mode 100644 index 0000000000..8155290ee9 --- /dev/null +++ b/src/communication/EmscriptenCommunication.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifdef __EMSCRIPTEN__ +#include "communication/EmscriptenCommunication.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Application.h" +#include "FffProcessor.h" +#include "Slice.h" +#include "utils/string.h" + +namespace cura +{ + +EmscriptenCommunication::EmscriptenCommunication(const std::vector& arguments) + : CommandLine(arguments) +{ + spdlog::info("Emscripten communication initialized"); + if (auto progress_flag = ranges::find(arguments_, "--progress_cb"); progress_flag != arguments_.end()) + { + progress_handler_ = *ranges::next(progress_flag); + } + if (auto slice_info_flag = ranges::find(arguments_, "--slice_info_cb"); slice_info_flag != arguments_.end()) + { + slice_info_handler_ = *ranges::next(slice_info_flag); + } + if (auto gcode_header_flag = ranges::find(arguments_, "--gcode_header_cb"); gcode_header_flag != arguments_.end()) + { + gcode_header_handler_ = *ranges::next(gcode_header_flag); + } +} + +void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const +{ + emscripten_run_script(fmt::format("globalThis[\"{}\"](\"{}\")", gcode_header_handler_, convertTobase64(prefix)).c_str()); +} + +void EmscriptenCommunication::sendProgress(double progress) const +{ + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", progress_handler_, progress).c_str()); +} + +std::string EmscriptenCommunication::createSliceInfoMessage() +{ + // Construct a string with rapidjson containing the slice information + rapidjson::Document doc; + auto& allocator = doc.GetAllocator(); + 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 }, + { "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(feature.c_str(), allocator); + rapidjson::Value feature_duration(time_estimates[static_cast(duration_idx)]); + 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 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); + + // Serialize the JSON document to a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +void EmscriptenCommunication::sliceNext() +{ + CommandLine::sliceNext(); + auto slice_info = createSliceInfoMessage(); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", slice_info_handler_, slice_info).c_str()); +}; + +} // namespace cura + +#endif // __EMSCRIPTEN__ diff --git a/src/geometry/PartsView.cpp b/src/geometry/PartsView.cpp index 5e080b2aea..c1370895c5 100644 --- a/src/geometry/PartsView.cpp +++ b/src/geometry/PartsView.cpp @@ -3,6 +3,9 @@ #include "geometry/PartsView.h" +#include +#include + #include "geometry/Polygon.h" #include "geometry/SingleShape.h" 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..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" @@ -93,17 +94,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/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/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_); } 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" 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..a3c52d6509 --- /dev/null +++ b/tests/FffGcodeWriterTest.cpp @@ -0,0 +1,206 @@ +// 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 +#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 + +// 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 outer_square; + // Square that fits wholly inside the above square + Shape inner_square; + + FffGcodeWriterTest() + : fff_gcode_writer() + { + 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)); + 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)); + + Application::getInstance().communication_ = std::make_shared(); + } + + SliceDataStorage* setUpStorage() + { + 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; + + 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); + } + + settings->add("infill_line_distance", "10"); + + 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 = { { outer_square } }; + part.infill_area = outer_square; + + 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 + ); + + /* 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; + 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 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/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; 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); diff --git a/tests/test_default_settings.txt b/tests/test_default_settings.txt new file mode 100644 index 0000000000..34b5d53147 --- /dev/null +++ b/tests/test_default_settings.txt @@ -0,0 +1,1244 @@ +day=Fri +time=22:49:29 +print_temperature=210 +quality_changes_name=Oliver lightweight +material_crystallinity=False +raft_surface_line_width=0.4 +speed=0 +raft_surface_speed=40.0 +raft_surface_fan_speed=100.0 +material_print_temp_prepend=False +raft_interface_infill_overlap=0 +machine_name=Ultimaker 2+ +material_break_preparation_retracted_position=-16 +infill_randomize_start_location=False +support_structure=normal +acceleration_print_layer_0=3000 +cross_support_density_image= +retraction_prime_speed=25 +material_print_temperature_layer_0=210 +raft_surface_extruder_nr=0 +travel_retract_before_outer_wall=False +support_interface_material_flow=95.0 +minimum_roof_area=1 +alternate_carve_order=True +retraction_min_travel=5 +roofing_monotonic=True +machine_max_feedrate_y=300 +prime_blob_enable=False +cool_fan_speed_min=100.0 +support_bottom_enable=True +raft_base_jerk=20 +support_bottom_offset=0.8 +speed_equalize_flow_width_factor=110.0 +gradual_infill_step_height=1.5 +top_bottom_pattern=lines +material_anti_ooze_retraction_speed=5 +support_bottom_height=0.5 +machine_extruders_share_nozzle=False +wall_x_material_flow_layer_0=95.0 +minimum_support_area=0.0 +skin_overlap_mm=0.08 +min_skin_width_for_expansion=5.510910596163089e-17 +top_thickness=0.8 +flow_rate_extrusion_offset_factor=100 +material_alternate_walls=False +infill_pattern=grid +raft_interface_wall_count=0 +default_material_bed_temperature=60 +support_xy_distance_overhang=0.1 +conical_overhang_angle=50 +raft_interface_margin=15 +brim_width=8.0 +cutting_mesh=False +support_tree_top_rate=10 +multiple_mesh_overlap=0.15 +bridge_skin_speed=30.0 +material_bed_temperature_layer_0=110 +wall_x_material_flow=100 +material_final_print_temperature=195 +machine_max_feedrate_x=300 +prime_tower_base_size=10 +material_print_temp_wait=False +support_roof_offset=0.8 +raft_airgap=0.25 +meshfix_maximum_travel_resolution=0.8 +machine_steps_per_mm_e=1600 +jerk_travel=20 +lightning_infill_prune_angle=40 +machine_max_feedrate_e=45 +print_temp_warn_limit=3.0 +machine_height=205 +skirt_brim_speed=15.0 +wipe_retraction_extra_prime_amount=0 +support_interface_priority=interface_area_overwrite_support_area +machine_max_acceleration_x=9000 +machine_nozzle_size=0.4 +wall_overhang_angle=90 +support_top_distance=0.36 +support_interface_pattern=zigzag +material_break_retracted_position=-50 +wipe_retraction_retract_speed=25 +cool_min_temperature=200 +z_seam_position=back +support_interface_offset=0.8 +jerk_wall_0=20 +machine_depth=216.0 +material_surface_energy=100 +relative_extrusion=False +support_angle=75 +support_extruder_nr=0 +raft_base_wall_count=1 +zig_zaggify_support=True +support_roof_line_distance=0.4 +support_skip_some_zags=False +meshfix_maximum_deviation=0.04 +support_infill_sparse_thickness=0.15 +material_flow=100 +material_is_support_material=False +jerk_roofing=20 +ironing_enabled=False +wipe_move_distance=20 +raft_smoothing=5 +machine_width=200.5 +support_mesh_drop_down=True +infill_overlap=10 +min_infill_area=0 +material_shrinkage_percentage_xy=100.0 +machine_steps_per_mm_z=50 +initial_bottom_layers=6 +material_brand=empty_brand +wipe_retraction_prime_speed=25 +wall_0_material_flow=100 +extruder_prime_pos_y=0 +machine_feeder_wheel_diameter=10.0 +slicing_tolerance=middle +bridge_wall_coast=0 +machine_nozzle_heat_up_speed=2.0 +speed_topbottom=30.0 +support_roof_extruder_nr=0 +prime_tower_min_shell_thickness=0.4 +interlocking_enable=False +jerk_prime_tower=20 +retraction_hop=1 +initial_extruder_nr=0 +support_bottom_angles=[ ] +connect_infill_polygons=False +center_object=False +wall_0_material_flow_layer_0=110.00000000000001 +raft_interface_fan_speed=50.0 +support_tower_roof_angle=0 +raft_surface_remove_inside_corners=False +material_bed_temp_wait=True +support_interface_wall_count=1 +support_interface_angles=[ ] +jerk_travel_enabled=False +material_flush_purge_speed=0.5 +raft_surface_smoothing=5 +acceleration_travel=5000 +support_roof_height=0.5 +raft_flow=100.0 +adhesion_extruder_nr=-1 +printer_settings=0 +raft_remove_inside_corners=False +retraction_count_max=25 +jerk_infill=20 +bridge_fan_speed=100 +minimum_polygon_circumference=1.0 +machine_extruders_share_heater=False +bv_temp_warn_limit=7.5 +retraction_retract_speed=25 +brim_line_count=20 +raft_surface_line_spacing=0.4 +print_sequence=all_at_once +bridge_fan_speed_2=100.0 +retraction_extrusion_window=1 +machine_nozzle_expansion_angle=45 +top_layers=6 +machine_max_jerk_e=5.0 +meshfix_fluid_motion_shift_distance=0.1 +acceleration_wall=3000 +wall_material_flow=100 +skirt_height=3 +machine_steps_per_mm_x=50 +ppr_enable=False +support_bottom_stair_step_width=5.0 +alternate_extra_perimeter=False +skin_edge_support_thickness=0.6 +retraction_extra_prime_amount=0 +support_line_distance=13.333333333333334 +ooze_shield_dist=2 +prime_tower_position_x=197.4 +acceleration_travel_enabled=False +infill_mesh=False +travel=0 +meshfix_union_all=True +z_seam_y=216.0 +raft_base_extruder_nr=0 +raft_base_smoothing=5 +acceleration_wall_0=3000 +fill_outline_gaps=True +machine_show_variants=True +meshfix_fluid_motion_enabled=True +blackmagic=0 +speed_support_bottom=30.0 +material_id=empty_material +raft_surface_layers=2 +adaptive_layer_height_variation=0.1 +bridge_wall_min_length=1.0 +prime_tower_base_curve_magnitude=2 +support_roof_material_flow=95.0 +infill_mesh_order=0 +material=0 +material_end_of_filament_purge_speed=0.5 +wipe_retraction_speed=25 +prime_tower_min_volume=6 +resolution=0 +infill_wipe_dist=0.1 +jerk_print=20 +support_brim_enable=False +skirt_brim_material_flow=100 +skirt_brim_line_width=0.4 +raft_interface_z_offset=0.0 +machine_center_is_zero=False +adaptive_layer_height_enabled=False +support_interface_height=0.5 +wall_extruder_nr=-1 +min_odd_wall_line_width=0.34 +adhesion_type=skirt +acceleration_support_infill=3000 +min_wall_line_width=0.34 +draft_shield_height_limitation=limited +wall_0_material_flow_roofing=100 +wall_distribution_count=1 +material_name=empty +layer_start_x=0.0 +support_tower_maximum_supported_diameter=3.0 +acceleration_support=3000 +raft_jerk=20 +switch_extruder_retraction_speed=20 +machine_max_jerk_z=0.4 +raft_surface_flow=100.0 +jerk_print_layer_0=20 +brim_smart_ordering=True +roofing_material_flow=100 +machine_extruder_count=1 +acceleration_infill=3000 +layer_start_y=0.0 +acceleration_support_interface=3000 +bottom_layers=6 +support_enable=False +extruder_prime_pos_z=0 +support_bottom_line_distance=0.4 +machine_heated_build_volume=False +retraction_enable=True +jerk_support_bottom=20 +coasting_enable=False +_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled=False +magic_mesh_surface_mode=normal +zig_zaggify_infill=False +support_interface_line_width=0.4 +skin_material_flow=95.0 +lightning_infill_overhang_angle=40 +wall_transition_angle=10 +coasting_min_volume=0.8 +max_extrusion_before_wipe=10 +support_interface_extruder_nr=0 +machine_use_extruder_offset_to_offset_coords=True +infill_support_enabled=False +material_print_temperature=210 +acceleration_prime_tower=3000 +material_flow_layer_0=100 +roofing_line_width=0.4 +raft_base_thickness=0.1875 +retraction_hop_only_when_collides=False +infill_before_walls=True +inset_direction=outside_in +wall_x_material_flow_roofing=100 +material_flush_purge_length=60 +support_conical_angle=30 +raft_interface_flow=100.0 +adaptive_layer_height_threshold=0.2 +flow_warn_limit=15.0 +machine_max_acceleration_z=100 +speed_travel=120 +machine_shape=rectangular +max_skin_angle_for_expansion=90 +ooze_shield_enabled=False +switch_extruder_retraction_amount=20 +wipe_retraction_enable=True +material_flow_temp_graph=[[3.5, 200],[7.0, 240]] +print_bed_temperature=90 +raft_surface_jerk=20 +support_mesh=False +support=0 +machine_firmware_retract=False +skirt_line_count=1 +jerk_travel_layer_0=20.0 +raft_base_speed=15 +wall_line_count=2 +cool_fan_speed_0=0 +support_wall_count=0 +interlocking_orientation=22.5 +infill_sparse_density=20 +bridge_skin_density_2=100 +acceleration_support_bottom=3000 +bridge_skin_density=80 +_plugin__curaenginegradualflow__0_1_0__gradual_flow_discretisation_step_size=0.2 +skin_outline_count=1 +acceleration_travel_layer_0=5000.0 +material_extrusion_cool_down_speed=0.7 +mesh_position_z=0 +machine_gcode_flavor=RepRap (Marlin/Sprinter) +mesh_position_x=0 +quality_name=Extra Fine +mesh_rotation_matrix=[[1,0,0], [0,1,0], [0,0,1]] +meshfix_extensive_stitching=False +speed_wall=30.0 +support_meshes_present=False +meshfix_fluid_motion_angle=15 +speed_wall_0=30.0 +switch_extruder_extra_prime_amount=0 +support_brim_line_count=5 +build_volume_temperature=28 +draft_shield_dist=3 +acceleration_topbottom=3000 +extruder_prime_pos_x=0 +flow_anomaly_limit=25.0 +command_line_settings=0 +support_supported_skin_fan_speed=100 +wall_thickness=0.8 +top_bottom_pattern_0=lines +ironing_line_spacing=0.1 +skin_edge_support_layers=4 +bottom_thickness=0.8 +meshfix_union_all_remove_holes=False +cool_fan_full_at_height=0.3 +mold_angle=40 +machine_head_with_fans_polygon=[[-44, 14], [-44, -34], [64, 14], [64, -34]] +material_no_load_move_factor=0.940860215 +infill_offset_x=0 +jerk_support_infill=20 +infill_support_angle=40 +experimental=0 +acceleration_skirt_brim=3000 +support_connect_zigzags=True +bridge_skin_density_3=100 +raft_base_flow=100.0 +cool_min_layer_time=6 +jerk_support_interface=20 +speed_print_layer_0=15.0 +nozzle_offsetting_for_disallowed_areas=True +coasting_speed=90 +support_z_seam_min_distance=0.8 +z_seam_on_vertex=False +support_xy_distance=0.4 +prime_tower_brim_enable=False +seam_overhang_angle=75 +wipe_pause=0 +conical_overhang_enabled=False +retraction_combing_max_distance=15 +support_roof_pattern=zigzag +support_roof_angles=[ ] +coasting_volume=0.064 +interlocking_boundary_avoidance=2 +acceleration_print=3000 +machine_minimum_feedrate=0.0 +remove_empty_first_layers=True +jerk_topbottom=20 +brim_gap=0 +smooth_spiralized_contours=True +acceleration_enabled=False +support_bottom_stair_step_height=0 +travel_avoid_other_parts=True +_plugin__curaenginegradualflow__0_1_0__reset_flow_duration=2.0 +small_feature_speed_factor_0=50 +machine_nozzle_id=unknown +prime_tower_size=20 +acceleration_support_roof=3000 +support_bottom_pattern=zigzag +machine_nozzle_head_distance=5 +wipe_retraction_amount=6.5 +support_tree_tip_diameter=0.8 +jerk_ironing=20 +gradual_infill_steps=0 +support_roof_wall_count=1 +prime_tower_max_bridging_distance=5 +skirt_gap=3 +support_line_width=0.4 +bridge_sparse_infill_max_density=0 +support_tree_bp_diameter=7.5 +skin_angles=[] +support_tree_angle=75 +jerk_layer_0=20 +lightning_infill_support_angle=40 +infill_line_distance=4.0 +minimum_interface_area=1.0 +ironing_monotonic=False +small_skin_width=0.8 +support_infill_density_multiplier_initial_layer=1 +_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration=1 +support_brim_width=4 +support_tree_branch_diameter=5 +support_initial_layer_line_distance=13.333333333333334 +raft_interface_extruder_nr=0 +bridge_wall_material_flow=100 +lightning_infill_straightening_angle=40 +machine_endstop_positive_direction_x=False +_plugin__curaenginegradualflow__0_1_0__layer_0_max_flow_acceleration=1 +wall_transition_filter_deviation=0.1 +meshfix_keep_open_polygons=False +raft_interface_line_spacing=0.65 +draft_shield_enabled=False +anti_overhang_mesh=False +infill_material_flow=100 +roofing_extruder_nr=-1 +conical_overhang_hole_size=0 +skin_overlap=20 +material_break_speed=25 +retract_at_layer_change=False +support_roof_line_width=0.4 +machine_disallowed_areas=[[[-115, 112.5], [-78, 112.5], [-80, 102.5], [-115, 102.5]], [[115, 112.5], [115, 102.5], [105, 102.5], [103, 112.5]], [[-115, -112.5], [-115, -104.5], [-84, -104.5], [-82, -112.5]], [[115, -112.5], [108, -112.5], [110, -104.5], [115, -104.5]]] +support_tower_diameter=3.0 +carve_multiple_volumes=False +support_interface_enable=True +machine_max_acceleration_y=9000 +optimize_wall_printing_order=True +retraction_hop_after_extruder_switch_height=1 +bottom_skin_expand_distance=0.8 +min_feature_size=0.1 +material_break_preparation_speed=2 +skin_line_width=0.4 +raft_margin=15 +extruders_enabled_count=1 +machine_extruders_shared_nozzle_initial_retraction=0 +interlocking_beam_width=0.8 +cool_fan_speed=100.0 +machine_max_jerk_xy=20.0 +support_infill_extruder_nr=0 +raft_interface_infill_overlap_mm=0.0 +raft_base_line_width=0.5 +wipe_hop_speed=10 +prime_tower_enable=False +magic_fuzzy_skin_thickness=0.3 +support_conical_enabled=False +speed_wall_0_roofing=30.0 +support_xy_overrides_z=z_overrides_xy +magic_spiralize=False +wall_transition_filter_distance=100 +wall_0_inset=0 +bv_temp_anomaly_limit=10.0 +speed_slowdown_layers=2 +print_temp_anomaly_limit=7.0 +support_tree_rest_preference=buildplate +cool_lift_head=False +adaptive_layer_height_variation_step=0.01 +cool_fan_enabled=True +ironing_flow=10.0 +minimum_bottom_area=1.0 +support_tree_min_height_to_model=3 +material_bed_temperature=90 +user_defined_print_order_enabled=False +acceleration_layer_0=3000 +raft_surface_thickness=0.15 +speed_infill=60 +support_z_seam_away_from_model=True +cool_min_layer_time_fan_speed_max=11 +machine_max_acceleration_e=10000 +support_bottom_extruder_nr=0 +roofing_pattern=lines +jerk_wall_x_roofing=20 +top_skin_preshrink=0.8 +speed_wall_x=30.0 +infill_line_width=0.4 +dual=0 +cool_during_extruder_switch=unchanged +wipe_hop_enable=False +machine_end_gcode=;end +support_bottom_line_width=0.4 +initial_layer_line_width_factor=100.0 +raft_surface_infill_overlap=0 +prime_tower_line_width=0.4 +meshfix_fluid_motion_small_distance=0.01 +material_anti_ooze_retracted_position=-4 +layer_height=0.15 +raft_base_margin=15 +speed_wall_x_roofing=30.0 +speed_layer_0=15.0 +skin_no_small_gaps_heuristic=False +jerk_wall_0_roofing=20 +cross_infill_pocket_size=4.0 +min_bead_width=0.34 +bridge_skin_material_flow=95.0 +mold_roof_height=0.5 +raft_interface_smoothing=5 +machine_nozzle_tip_outer_diameter=1 +wall_line_width_0=0.4 +speed_z_hop=10 +brim_inside_margin=1.6 +material_diameter=2.85 +bridge_enable_more_layers=False +min_even_wall_line_width=0.34 +raft_base_remove_inside_corners=False +support_tree_max_diameter=25 +small_feature_max_length=0.0 +support_roof_density=100 +jerk_skirt_brim=20 +raft_base_line_spacing=1.0 +raft_wall_count=1 +raft_interface_layers=1 +meshfix=0 +machine_heat_zone_length=20 +material_bed_temp_prepend=False +prime_tower_raft_base_line_spacing=1.0 +switch_extruder_prime_speed=20 +wall_0_wipe_dist=0.2 +material_standby_temperature=110 +interlocking_depth=2 +magic_fuzzy_skin_outside_only=False +raft_surface_wall_count=0 +machine_min_cool_heat_time_window=50.0 +speed_travel_layer_0=120 +raft_base_infill_overlap_mm=0.0 +travel_avoid_supports=False +interlocking_beam_layer_count=2 +raft_base_infill_overlap=0 +material_end_of_filament_purge_length=20 +raft_interface_acceleration=3000 +support_tree_branch_reach_limit=30 +support_bottom_material_flow=95.0 +raft_interface_speed=27.5 +raft_surface_infill_overlap_mm=0.0 +bridge_skin_support_threshold=50 +support_material_flow=100 +wall_overhang_speed_factor=100 +material_type=empty +wall_line_width_x=0.4 +raft_base_acceleration=3000 +machine_steps_per_mm_y=50 +prime_tower_wipe_enabled=True +wall_0_extruder_nr=-1 +retraction_hop_enabled=False +xy_offset=-0.015 +default_material_print_temperature=210 +meshfix_maximum_extrusion_area_deviation=50000 +infill_multiplier=1 +top_bottom=0 +support_fan_enable=False +acceleration_wall_x=3000 +prime_tower_flow=100 +infill_overlap_mm=0.04 +speed_support_infill=45.0 +support_type=buildplate +raft_interface_line_width=0.45 +hole_xy_offset_max_diameter=0 +cool_fan_full_layer=2 +roofing_angles=[] +retraction_amount=6.5 +wall_x_extruder_nr=-1 +support_tree_limit_branch_reach=True +xy_offset_layer_0=-0.095 +material_break_temperature=50 +machine_acceleration=3000 +raft_acceleration=3000 +z_seam_x=100.25 +skirt_brim_extruder_nr=-1 +raft_base_fan_speed=0 +support_tree_max_diameter_increase_by_merges_when_support_to_model=1 +speed_support_interface=30.0 +clean_between_layers=False +travel_speed=120 +z_seam_corner=z_seam_corner_none +support_tree_branch_diameter_angle=7 +prime_tower_base_height=6 +machine_nozzle_cool_down_speed=2.0 +jerk_wall=20 +skin_monotonic=False +material_maximum_park_duration=300 +bridge_skin_material_flow_3=95.0 +small_hole_max_size=0 +prime_tower_mode=interleaved +machine_heated_bed=True +wall_line_width=0.4 +gradual_support_infill_steps=0 +raft_interface_thickness=0.16875 +machine_endstop_positive_direction_y=False +support_bottom_distance=0 +acceleration_roofing=3000 +raft_interface_jerk=20 +retraction_speed=25 +meshfix_maximum_resolution=0.5 +expand_skins_expand_distance=0.8 +support_infill_rate=3 +support_extruder_nr_layer_0=0 +bridge_skin_speed_3=30.0 +infill=0 +magic_fuzzy_skin_point_density=1.25 +hole_xy_offset=0 +infill_wall_line_count=0 +speed_print=60 +jerk_enabled=False +bridge_settings_enabled=True +material_break_preparation_temperature=210 +skin_material_flow_layer_0=95 +platform_adhesion=0 +top_bottom_thickness=0.8 +material_guid= +raft_surface_z_offset=0.0 +ironing_pattern=zigzag +machine_buildplate_type=glass +support_join_distance=2.0 +machine_nozzle_temp_enabled=True +infill_enable_travel_optimization=False +gantry_height=52 +speed_ironing=20.0 +ppr=0 +roofing_layer_count=1 +support_conical_min_width=5.0 +raft_surface_monotonic=True +shell=0 +acceleration_ironing=3000 +line_width=0.4 +mold_enabled=False +magic_fuzzy_skin_enabled=False +support_pattern=zigzag +switch_extruder_retraction_speeds=20 +small_feature_speed_factor=50 +sub_div_rad_add=0.4 +acceleration_wall_0_roofing=3000 +top_skin_expand_distance=0.8 +z_seam_relative=False +machine_max_feedrate_z=40 +prime_tower_position_y=192.9 +jerk_support=20 +mesh_position_y=0 +retraction_hop_after_extruder_switch=True +jerk_wall_x=20 +infill_offset_y=0 +cool_fan_speed_max=100 +raft_surface_acceleration=3000 +travel_avoid_distance=0.625 +cool_min_speed=6 +skin_preshrink=0.8 +layer_height_0=0.3 +support_infill_angles=[ ] +retraction_combing=no_outer_surfaces +support_zag_skip_count=2 +material_shrinkage_percentage_z=100.0 +speed_support=45.0 +speed_prime_tower=60 +speed_roofing=30.0 +support_offset=0.8 +machine_settings=0 +date=19-07-2024 +jerk_support_roof=20 +material_shrinkage_percentage=100.0 +bridge_fan_speed_3=100.0 +support_tree_angle_slow=50.0 +raft_speed=15 +cooling=0 +brim_replaces_support=True +skirt_brim_minimal_length=250 +support_interface_density=100 +bridge_wall_speed=30.0 +z_seam_type=sharpest_corner +support_bottom_density=100 +support_bottom_wall_count=1 +speed_support_roof=30.0 +layer_0_z_overlap=0.125 +material_initial_print_temperature=200 +material_adhesion_tendency=10 +magic_fuzzy_skin_point_dist=0.8 +brim_location=outside +ooze_shield_angle=60 +bridge_skin_speed_2=30.0 +extruder_prime_pos_abs=False +machine_scale_fan_speed_zero_to_one=False +support_bottom_stair_step_min_slope=10.0 +machine_start_gcode=;Testing +wipe_brush_pos_x=100 +machine_always_write_active_tool=False +bridge_skin_material_flow_2=95.0 +infill_angles=[ ] +gradual_support_infill_step_height=0.6 +top_bottom_extruder_nr=-1 +wipe_hop_amount=1 +group_outer_walls=True +raft_surface_margin=15 +infill_sparse_thickness=0.15 +mold_width=5 +acceleration_wall_x_roofing=3000 +support_roof_enable=False +connect_skin_polygons=False +flow_rate_max_extrusion_offset=0 +infill_extruder_nr=-1 +wipe_repeat_count=5 +support_use_towers=False +support_skip_zag_per_mm=20 +support_z_distance=0.36 +nozzle_disallowed_areas=[] +bottom_skin_preshrink=0.8 +small_skin_on_surface=False +ironing_inset=0.38 +machine_endstop_positive_direction_z=True +raft_interface_remove_inside_corners=False +draft_shield_height=50.0 +raft_fan_speed=0 +ironing_only_highest_layer=False +wall_transition_length=0.4 +cross_infill_density_image= +-e0 material_crystallinity=False +raft_surface_line_width=0.4 +speed=0 +raft_surface_speed=40.0 +raft_surface_fan_speed=100.0 +raft_interface_infill_overlap=0 +material_break_preparation_retracted_position=-16 +skirt_gap=1.0 +prime_tower_max_bridging_distance=5 +machine_heat_zone_length=20 +raft_surface_line_spacing=0.4 +brim_line_count=3 +retraction_retract_speed=45.0 +small_feature_max_length=0.0 +material_break_retracted_position=-50 +min_even_wall_line_width=0.2125 +support_tree_max_diameter=25 +lightning_infill_prune_angle=45.0 +bridge_skin_material_flow=95.0 +support_wall_count=0 +prime_tower_min_volume=6 +wipe_retraction_speed=45.0 +material_end_of_filament_purge_speed=0.5 +skin_no_small_gaps_heuristic=False +wipe_hop_speed=10 +speed_print_layer_0=20.0 +raft_base_margin=15 +material_anti_ooze_retracted_position=-4 +extruder_prime_pos_z=0 +support_bottom_line_distance=0.4 +support_infill_density_multiplier_initial_layer=1 +_plugin__curaenginegradualflow__0_1_0__max_flow_acceleration=1 +support_tree_min_height_to_model=3 +minimum_bottom_area=1.0 +raft_interface_infill_overlap_mm=0.0 +support_tree_rest_preference=buildplate +cool_lift_head=False +print_temp_anomaly_limit=7.0 +machine_extruder_end_pos_x=0 +zig_zaggify_support=True +support_roof_line_distance=0.4 +support_infill_sparse_thickness=0.15 +ironing_enabled=False +wipe_move_distance=20 +jerk_print_layer_0=20 +min_infill_area=0 +machine_steps_per_mm_z=50 +initial_bottom_layers=2 +material_brand=Olivers +wipe_retraction_prime_speed=45.0 +wall_0_material_flow=100 +extruder_prime_pos_y=0 +machine_feeder_wheel_diameter=10.0 +switch_extruder_retraction_amount=20 +wipe_retraction_enable=True +skirt_line_count=1 +slicing_tolerance=middle +bridge_wall_coast=0 +raft_surface_infill_overlap=0 +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 +support_bottom_angles=[ ] +connect_infill_polygons=False +center_object=False +acceleration_print_layer_0=3000 +raft_interface_fan_speed=50.0 +support_tower_roof_angle=0 +support_interface_wall_count=1 +support_interface_angles=[ ] +raft_interface_layers=1 +z_seam_y=216.0 +wall_overhang_angle=90 +material_flush_purge_speed=0.5 +raft_flow=100.0 +printer_settings=0 +retraction_count_max=25 +jerk_infill=20 +bridge_fan_speed=100 +bridge_skin_speed=40.0 +machine_nozzle_offset_y=0 +material_anti_ooze_retraction_speed=5 +skirt_height=3 +roofing_pattern=lines +machine_steps_per_mm_x=50 +alternate_extra_perimeter=False +support_brim_line_count=5 +support_line_distance=13.333333333333334 +support_bottom_line_width=0.4 +initial_layer_line_width_factor=200.0 +support_roof_height=0.5 +acceleration_travel=5000 +travel=0 +meshfix_union_all=True +meshfix_fluid_motion_enabled=True +blackmagic=0 +speed_support_bottom=30.0 +raft_surface_layers=2 +support_roof_pattern=zigzag +support_roof_angles=[ ] +speed_wall_x=50.0 +infill_line_width=0.4 +support_bottom_offset=0.8 +wipe_pause=0 +raft_base_wall_count=1 +top_skin_preshrink=0.8 +jerk_wall_x_roofing=20 +gradual_infill_steps=0 +wall_line_width_0=0.4 +wipe_retraction_amount=3.5 +support_tree_tip_diameter=0.8 +jerk_ironing=20 +mesh_position_z=0 +bridge_skin_density_2=100 +acceleration_support_bottom=3000 +infill_sparse_density=5 +material_extrusion_cool_down_speed=0.7 +jerk_wall_0_roofing=20 +support_roof_material_flow=95.0 +meshfix=0 +support_roof_density=100 +jerk_skirt_brim=20 +meshfix_maximum_deviation=0.025 +support_skip_some_zags=False +infill_before_walls=False +inset_direction=inside_out +wall_x_material_flow_roofing=100 +material_flush_purge_length=60 +support_conical_angle=30 +raft_jerk=20 +switch_extruder_retraction_speed=20 +raft_surface_flow=100.0 +support_bottom_enable=True +support_roof_wall_count=1 +_plugin__curaenginegradualflow__0_1_0__gradual_flow_enabled=False +magic_mesh_surface_mode=normal +zig_zaggify_infill=False +support_interface_line_width=0.4 +cool_fan_speed_0=0 +wall_line_count=2 +min_odd_wall_line_width=0.2125 +brim_inside_margin=1.6 +speed_z_hop=10 +speed_travel=180.0 +machine_nozzle_offset_x=0 +support_tree_angle=75 +layer_start_x=0.0 +support_tower_maximum_supported_diameter=3.0 +acceleration_support=3000 +wall_distribution_count=1 +jerk_layer_0=20 +lightning_infill_support_angle=65.0 +infill_line_distance=60.0 +minimum_interface_area=1.0 +material_standby_temperature=175 +skin_material_flow=95.0 +lightning_infill_overhang_angle=65.0 +raft_interface_z_offset=0.0 +machine_extruder_start_code= +support_xy_distance_overhang=0.1 +roofing_monotonic=True +support_line_width=0.4 +bridge_sparse_infill_max_density=0 +meshfix_union_all_remove_holes=False +infill_support_enabled=False +material_flow_layer_0=100 +roofing_line_width=0.4 +speed_layer_0=10.0 +speed_wall_x_roofing=50.0 +dual=0 +support_bottom_stair_step_height=0 +roofing_material_flow=100 +acceleration_infill=3000 +bottom_layers=2 +material_print_temperature_layer_0=260 +machine_extruder_end_pos_y=0 +wall_transition_angle=10 +coasting_min_volume=0.17 +max_extrusion_before_wipe=10 +retraction_enable=True +jerk_support_bottom=20 +coasting_enable=False +material_print_temperature=250 +acceleration_prime_tower=3000 +coasting_volume=0.1 +acceleration_travel_layer_0=5000.0 +machine_extruder_start_pos_x=0 +mesh_position_x=0 +mesh_rotation_matrix=[[1,0,0], [0,1,0], [0,0,1]] +meshfix_extensive_stitching=False +speed_wall=50.0 +material_flow_temp_graph=[[3.5, 200],[7.0, 240]] +meshfix_fluid_motion_angle=15 +speed_wall_0=45.0 +switch_extruder_extra_prime_amount=0 +acceleration_topbottom=3000 +extruder_prime_pos_x=0 +material_surface_energy=100 +experimental=0 +acceleration_skirt_brim=3000 +support_supported_skin_fan_speed=100 +support_connect_zigzags=True +bridge_skin_density_3=100 +raft_base_flow=100.0 +raft_interface_wall_count=0 +wipe_hop_enable=False +cool_min_layer_time=2 +extruder_nr=0 +jerk_topbottom=20 +brim_gap=0 +wall_material_flow=100 +acceleration_wall=3000 +support_xy_distance=0.4 +seam_overhang_angle=75 +ironing_monotonic=False +small_skin_width=0.8 +coasting_speed=90 +support_z_seam_min_distance=0.8 +meshfix_keep_open_polygons=False +wall_transition_filter_deviation=0.0625 +z_seam_on_vertex=False +acceleration_support_roof=3000 +support_bottom_pattern=zigzag +machine_nozzle_head_distance=5 +material=0 +mold_angle=40 +material_no_load_move_factor=0.940860215 +raft_airgap=0.25 +infill_offset_x=0 +jerk_support_infill=20 +infill_support_angle=40 +ironing_line_spacing=0.1 +ironing_flow=10.0 +cool_fan_enabled=True +bottom_thickness=0.3 +bridge_wall_material_flow=100 +lightning_infill_straightening_angle=60.0 +raft_base_line_spacing=1.0 +raft_wall_count=1 +support_brim_width=4 +support_tree_branch_diameter=5 +support_initial_layer_line_distance=13.333333333333334 +retraction_extrusion_window=1 +bridge_fan_speed_2=100.0 +bridge_skin_density=80 +machine_endstop_positive_direction_x=False +_plugin__curaenginegradualflow__0_1_0__layer_0_max_flow_acceleration=1 +machine_nozzle_tip_outer_diameter=0.8 +mold_roof_height=0.5 +connect_skin_polygons=False +skin_overlap=20 +material_break_speed=25 +print_temp_warn_limit=3.0 +jerk_roofing=20 +material_flow=100 +material_is_support_material=False +retract_at_layer_change=False +support_roof_line_width=0.4 +retraction_hop_after_extruder_switch_height=1 +machine_nozzle_size=0.25 +raft_margin=15 +raft_base_infill_overlap_mm=0.0 +travel_avoid_supports=False +min_skin_width_for_expansion=2.7554552980815445e-17 +machine_extruders_shared_nozzle_initial_retraction=0 +interlocking_beam_width=0.8 +retraction_min_travel=5 +infill_material_flow=100 +conical_overhang_hole_size=0 +cool_fan_speed=100.0 +raft_base_jerk=20 +raft_base_line_width=0.5 +min_bead_width=0.2125 +speed_wall_0_roofing=45.0 +raft_surface_jerk=20 +support_xy_overrides_z=z_overrides_xy +wall_transition_filter_distance=100 +wall_0_inset=0 +acceleration_support_interface=3000 +layer_start_y=0.0 +wall_0_material_flow_roofing=100 +ironing_pattern=zigzag +raft_surface_z_offset=0.0 +cross_infill_pocket_size=60.0 +acceleration_support_infill=3000 +min_wall_line_width=0.2125 +skin_outline_count=1 +_plugin__curaenginegradualflow__0_1_0__gradual_flow_discretisation_step_size=0.2 +conical_overhang_angle=62.0 +skin_angles=[] +support_tree_bp_diameter=7.5 +resolution=0 +support_angle=75 +top_bottom_pattern_0=lines +wall_thickness=1.2000000000000002 +infill_wipe_dist=0.1 +support_bottom_height=0.5 +support_brim_enable=False +small_feature_speed_factor_0=50 +_plugin__curaenginegradualflow__0_1_0__reset_flow_duration=2.0 +travel_avoid_other_parts=True +jerk_print=20 +switch_extruder_prime_speed=20 +wall_0_wipe_dist=0.125 +skin_preshrink=0.8 +support_interface_offset=0.8 +z_seam_position=back +skin_line_width=0.4 +material_break_preparation_speed=2 +jerk_travel_layer_0=20.0 +raft_base_speed=15 +machine_nozzle_id=unknown +jerk_support_interface=20 +magic_fuzzy_skin_outside_only=False +raft_surface_wall_count=0 +machine_min_cool_heat_time_window=50.0 +speed_travel_layer_0=180.0 +raft_base_infill_overlap=0 +material_end_of_filament_purge_length=20 +raft_interface_acceleration=3000 +support_tree_branch_reach_limit=30 +support_bottom_material_flow=95.0 +skin_edge_support_layers=0 +machine_extruder_end_pos_abs=False +acceleration_roofing=3000 +raft_interface_jerk=20 +retraction_speed=45.0 +raft_interface_speed=27.5 +raft_surface_infill_overlap_mm=0.0 +bridge_skin_support_threshold=50 +machine_extruder_start_pos_y=0 +support_material_flow=100 +wall_overhang_speed_factor=100 +material_type=PLA +command_line_settings=0 +flow_anomaly_limit=25.0 +machine_steps_per_mm_y=50 +meshfix_fluid_motion_small_distance=0.01 +prime_tower_line_width=0.4 +xy_offset=-0.06 +support_interface_pattern=zigzag +default_material_print_temperature=200 +meshfix_maximum_extrusion_area_deviation=50000 +cross_support_density_image= +bottom_skin_expand_distance=0.0 +min_feature_size=0.1 +support_fan_enable=False +acceleration_wall_x=3000 +prime_tower_flow=100 +infill_overlap_mm=0.2 +speed_support_infill=45.0 +retraction_hop_enabled=False +prime_tower_wipe_enabled=True +raft_interface_line_width=0.45 +hole_xy_offset_max_diameter=0 +cool_fan_full_layer=2 +roofing_angles=[] +infill_overlap=100.0 +support_interface_height=0.5 +retraction_amount=3.5 +support_tree_limit_branch_reach=True +xy_offset_layer_0=-0.17 +meshfix_maximum_travel_resolution=0.8 +material_break_temperature=60 +prime_tower_raft_base_line_spacing=1.0 +meshfix_fluid_motion_shift_distance=0.1 +top_layers=3 +raft_acceleration=3000 +z_seam_x=100.25 +support_interface_material_flow=95.0 +raft_base_fan_speed=0 +max_skin_angle_for_expansion=90 +speed_support_interface=30.0 +support_tree_max_diameter_increase_by_merges_when_support_to_model=1 +clean_between_layers=False +z_seam_corner=z_seam_corner_none +support_interface_enable=True +brim_smart_ordering=True +machine_nozzle_cool_down_speed=2.0 +jerk_wall=20 +raft_interface_flow=100.0 +machine_extruder_start_code_duration=0 +jerk_support=20 +skin_monotonic=True +material_maximum_park_duration=7200 +bridge_skin_material_flow_3=95.0 +small_hole_max_size=0 +wall_line_width=0.4 +gradual_support_infill_steps=0 +raft_interface_thickness=0.16875 +machine_endstop_positive_direction_y=False +support_bottom_distance=0 +wall_0_material_flow_layer_0=110.00000000000001 +meshfix_maximum_resolution=0.6 +expand_skins_expand_distance=0.8 +support_infill_rate=3 +support_bottom_stair_step_width=5.0 +bridge_skin_speed_3=40.0 +infill=0 +magic_fuzzy_skin_point_density=1.25 +hole_xy_offset=0 +infill_wall_line_count=0 +speed_print=60.0 +material_break_preparation_temperature=210 +material_alternate_walls=False +skin_material_flow_layer_0=95 +top_thickness=0.3 +machine_nozzle_temp_enabled=True +infill_enable_travel_optimization=False +speed_ironing=26.666666666666668 +ppr=0 +roofing_layer_count=0 +support_join_distance=2.0 +support_conical_min_width=5.0 +raft_surface_monotonic=True +shell=0 +acceleration_ironing=3000 +line_width=0.4 +mold_enabled=False +magic_fuzzy_skin_enabled=False +support_pattern=zigzag +switch_extruder_retraction_speeds=20 +small_feature_speed_factor=50 +sub_div_rad_add=0.4 +skin_overlap_mm=0.08 +acceleration_wall_0_roofing=3000 +top_skin_expand_distance=0.8 +z_seam_relative=False +mesh_position_y=0 +retraction_hop_after_extruder_switch=True +jerk_wall_x=20 +infill_offset_y=0 +cool_fan_speed_max=100 +raft_surface_acceleration=3000 +travel_avoid_distance=0.5 +cool_min_speed=10 +machine_extruder_cooling_fan_number=0 +bridge_enable_more_layers=False +material_diameter=1.75 +support_offset=0.8 +machine_settings=0 +optimize_wall_printing_order=True +jerk_support_roof=20 +bridge_fan_speed_3=100.0 +support_tree_angle_slow=50.0 +raft_speed=15 +support_interface_density=100 +bridge_wall_speed=40.0 +minimum_roof_area=1 +z_seam_type=random +machine_extruder_end_code_duration=0 +support_bottom_density=100 +acceleration_layer_0=3000 +support_conical_enabled=False +magic_fuzzy_skin_thickness=0.01 +retraction_combing_max_distance=15 +conical_overhang_enabled=False +support_bottom_wall_count=1 +speed_support_roof=30.0 +layer_0_z_overlap=0.125 +material_initial_print_temperature=260 +material_adhesion_tendency=0 +cool_fan_full_at_height=0.3 +machine_extruder_end_code= +bridge_skin_speed_2=40.0 +magic_fuzzy_skin_point_dist=0.8 +brim_location=outside +extruder_prime_pos_abs=False +support_bottom_stair_step_min_slope=10.0 +wipe_brush_pos_x=100 +gradual_support_infill_step_height=0.6 +machine_steps_per_mm_e=1600 +wipe_hop_amount=1 +group_outer_walls=True +raft_surface_margin=15 +infill_sparse_thickness=0.15 +mold_width=5 +retraction_hop_only_when_collides=False +raft_base_thickness=0.1875 +acceleration_wall_x_roofing=3000 +support_roof_enable=False +wipe_repeat_count=5 +support_use_towers=False +support_skip_zag_per_mm=20 +support_z_distance=0.36 +bottom_skin_preshrink=0.8 +small_skin_on_surface=False +ironing_inset=0.38 +machine_endstop_positive_direction_z=True +speed_support=45.0 +speed_prime_tower=60.0 +speed_roofing=40.0 +raft_fan_speed=0 +ironing_only_highest_layer=False +wall_transition_length=0.4 +cross_infill_density_image= +acceleration_print=3000 +support_tree_branch_diameter_angle=7 +cool_min_temperature=260 +wipe_retraction_retract_speed=45.0 +support_top_distance=0.36 +wipe_retraction_extra_prime_amount=0 +skirt_brim_speed=10.0 +flow_warn_limit=15.0 +jerk_travel=20 +support_tree_top_rate=10 +support=0 +machine_extruder_start_pos_abs=False +support_roof_offset=0.8 +raft_interface_line_spacing=0.65 +top_bottom=0 +infill_multiplier=1 +wall_x_material_flow=100 +multiple_mesh_overlap=0.15 +skirt_brim_line_width=0.4 +skirt_brim_material_flow=100 +brim_width=3.0 +raft_interface_margin=15 +infill_pattern=cubic +infill_angles=[ ] +bridge_skin_material_flow_2=95.0 +fill_outline_gaps=True +acceleration_wall_0=3000 +cool_min_layer_time_fan_speed_max=7 +wall_x_material_flow_layer_0=95.0 +minimum_support_area=0.0 +top_bottom_pattern=lines +gradual_infill_step_height=0.5 +raft_base_acceleration=3000 +wall_line_width_x=0.4 +speed_equalize_flow_width_factor=110.0 +cool_fan_speed_min=100.0 +prime_blob_enable=False +top_bottom_thickness=0.3 +material_guid=506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9 +platform_adhesion=0 +support_zag_skip_count=2 +support_infill_angles=[ ] +speed_infill=60.0 +support_z_seam_away_from_model=True +raft_surface_thickness=0.15 +retraction_prime_speed=45.0 +support_tower_diameter=3.0 +brim_replaces_support=True +skirt_brim_minimal_length=350.0 +cooling=0 +material_final_print_temperature=250 +infill_randomize_start_location=False +extruder_nr=0 + diff --git a/tests/utils/MathTest.cpp b/tests/utils/MathTest.cpp new file mode 100644 index 0000000000..71c5e8c8f9 --- /dev/null +++ b/tests/utils/MathTest.cpp @@ -0,0 +1,69 @@ +// 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."; +} + +// 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."; +} + +// 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."; +} + +// 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."; + +} + +// 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."; +} + +// NOLINTEND(*-magic-numbers) + +} // namespace cura