diff --git a/conanfile.py b/conanfile.py index b498c3b84d..b3e464843a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -40,7 +40,7 @@ class CuraEngineConan(ConanFile): def set_version(self): if not self.version: - self.version = "5.6.0-alpha" + self.version = "5.6.0-beta.1" def export_sources(self): copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder) diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index c38beba261..515c19ba21 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -15,6 +15,7 @@ #include "settings/PathConfigStorage.h" //For the MeshPathConfigs subclass. #include "utils/ExtrusionLine.h" //Processing variable-width paths. #include "utils/NoCopy.h" +#include "utils/gettime.h" namespace cura { @@ -26,7 +27,6 @@ class SliceDataStorage; class SliceMeshStorage; class SliceLayer; class SliceLayerPart; -class TimeKeeper; /*! * Secondary stage in Fused Filament Fabrication processing: The generated polygons are used in the gcode generation. @@ -140,6 +140,13 @@ class FffGcodeWriter : public NoCopy void writeGCode(SliceDataStorage& storage, TimeKeeper& timeKeeper); private: + struct ProcessLayerResult + { + LayerPlan* layer_plan; + double total_elapsed_time; + TimeKeeper::RegisteredTimes stages_times; + }; + /*! * \brief Set the FffGcodeWriter::fan_speed_layer_time_settings by * retrieving all settings from the global/per-meshgroup settings. @@ -211,7 +218,7 @@ class FffGcodeWriter : public NoCopy * \param total_layers The total number of layers. * \return The layer plans */ - LayerPlan& processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const; + ProcessLayerResult processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const; /*! * This function checks whether prime blob should happen for any extruder on the first layer. diff --git a/include/LayerPlanBuffer.h b/include/LayerPlanBuffer.h index db3d47ffa8..68d5e9e848 100644 --- a/include/LayerPlanBuffer.h +++ b/include/LayerPlanBuffer.h @@ -4,6 +4,9 @@ #ifndef LAYER_PLAN_BUFFER_H #define LAYER_PLAN_BUFFER_H +#include +#include + #include "ExtruderPlan.h" #include "LayerPlan.h" #include "Preheat.h" @@ -11,9 +14,6 @@ #include "settings/Settings.h" #include "settings/types/Duration.h" -#include -#include - namespace cura { @@ -36,7 +36,6 @@ class GCodeExport; class LayerPlanBuffer { friend class LayerPlan; - friend class LayerPlanBuffer; GCodeExport& gcode; Preheat preheat_config; //!< the nozzle and material temperature settings for each extruder train. diff --git a/include/PrimeTower.h b/include/PrimeTower.h index f338de0226..9989987ae5 100644 --- a/include/PrimeTower.h +++ b/include/PrimeTower.h @@ -179,17 +179,46 @@ class PrimeTower */ void addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder) const; + /*! + * \brief Add path plans for the prime tower extra outer rings to make the stronger base + * \param gcode_layer The gcode export to add the paths plans to + * \param extruder_nr The current extruder number + * \return True if something has actually been added, according to the extruder number + * and current layer. + */ bool addToGcode_base(LayerPlan& gcode_layer, const size_t extruder_nr) const; + /*! + * \brief Add path plans for the prime tower extra inner rings to increase bed adhesion + * \param gcode_layer The gcode export to add the paths plans to + * \param extruder_nr The current extruder number + * \return True if something has actually been added, according to the extruder number + * and current layer. + */ bool addToGcode_inset(LayerPlan& gcode_layer, const size_t extruder_nr) const; -#warning TBD documentation - void addToGcode_optimizedInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const; + /*! + * \brief Add path plans in the case an extruder is not to be actually primed, but we still + * want to print something to make the prime tower consistent. + * \param gcode_layer The gcode export to add the paths plans to + * \param extruders_to_prime_idx The indexes of the extra extruders which also don't require being primed on this layer + * \param current_extruder_nr The extruder currently being used + */ + void addToGcode_sparseInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const; + /*! + * \brief Find the list of extruders that don't actually need to be primed during this layer, and for which + * we want to print only the sparse infill to keep the prime tower consistent. + * \param gcode_layer The current gcode export + * \param required_extruder_prime The pre-computed list of extruders uses during this layer + * \param method The current prime tower strategy + * \param initial_list_idx A list potentially containing extruders that we already know can be used for + * sparse infill + * \return The indexes of extruders to be used for sparse infill + */ std::vector findExtrudersSparseInfill( LayerPlan& gcode_layer, const std::vector& required_extruder_prime, - const size_t current_extruder_nr, cura::PrimeTowerMethod method, const std::vector& initial_list_idx = {}) const; diff --git a/include/progress/Progress.h b/include/progress/Progress.h index 6b9c83d532..c1e20cc6c7 100644 --- a/include/progress/Progress.h +++ b/include/progress/Progress.h @@ -1,72 +1,102 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef PROGRESS_H #define PROGRESS_H +#include +#include #include +#include + +#include "utils/gettime.h" namespace cura { -class TimeKeeper; +struct LayerIndex; -#define N_PROGRESS_STAGES 7 +static constexpr size_t N_PROGRESS_STAGES = 7; /*! * Class for handling the progress bar and the progress logging. - * + * * The progress bar is based on a single slicing of a rather large model which needs some complex support; * the relative timing of each stage is currently based on that of the slicing of dragon_65_tilted_large.stl */ -class Progress +class Progress { public: /*! - * The stage in the whole slicing process + * The stage in the whole slicing process */ enum class Stage : unsigned int { - START = 0, - SLICING = 1, - PARTS = 2, - INSET_SKIN = 3, - SUPPORT = 4, - EXPORT = 5, - FINISH = 6 + START = 0, + SLICING = 1, + PARTS = 2, + INSET_SKIN = 3, + SUPPORT = 4, + EXPORT = 5, + FINISH = 6 }; + private: - static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage - static std::string names[N_PROGRESS_STAGES]; //!< name of each stage - static double accumulated_times [N_PROGRESS_STAGES]; //!< Time past before each stage + static constexpr std::array times{ + 0.0, // START = 0, + 5.269, // SLICING = 1, + 1.533, // PARTS = 2, + 71.811, // INSET_SKIN = 3 + 51.009, // SUPPORT = 4, + 154.62, // EXPORT = 5, + 0.1 // FINISH = 6 + }; + + static constexpr std::array names{ "start", "slice", "layerparts", "inset+skin", "support", "export", "process" }; + static std::array accumulated_times; //!< Time past before each stage static double total_timing; //!< An estimate of the total time + static std::optional first_skipped_layer; //!< The index of the layer for which we skipped time reporting /*! * Give an estimate between 0 and 1 of how far the process is. - * + * * \param stage The current stage of processing * \param stage_process How far we currently are in the \p stage * \return An estimate of the overall progress. */ - static float calcOverallProgress(Stage stage, float stage_progress); + static double calcOverallProgress(Stage stage, double stage_progress); + public: static void init(); //!< Initialize some values needed in a fast computation of the progress /*! * Message progress over the CommandSocket and to the terminal (if the command line arg '-p' is provided). - * + * * \param stage The current stage of processing * \param progress_in_stage Any number giving the progress within the stage * \param progress_in_stage_max The maximal value of \p progress_in_stage */ static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max); + /*! * Message the progress stage over the command socket. - * + * * \param stage The current stage * \param timeKeeper The stapwatch keeping track of the timings for each stage (optional) */ static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper); + + /*! + * Message the layer progress over the command socket and into logging output. + * + * \param layer_nr The processed layer number + * \param total_layers The total number of layers to be processed + * \param total_time The total layer processing time, in seconds + * \param stage The detailed stages time reporting for this layer + * \param skip_threshold The time threshold under which we consider that the full layer time reporting should be skipped + * because it is not relevant + */ + static void messageProgressLayer(LayerIndex layer_nr, size_t total_layers, double total_time, const TimeKeeper::RegisteredTimes& stages, double skip_threshold = 0.1); }; -} // name space cura -#endif//PROGRESS_H +} // namespace cura +#endif // PROGRESS_H diff --git a/include/utils/SVG.h b/include/utils/SVG.h index c4418ef86f..eec59d9d0a 100644 --- a/include/utils/SVG.h +++ b/include/utils/SVG.h @@ -1,5 +1,5 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef SVG_H #define SVG_H @@ -19,7 +19,8 @@ class FPoint3; class SVG : NoCopy { public: - enum class Color { + enum class Color + { BLACK, WHITE, GRAY, @@ -40,18 +41,20 @@ class SVG : NoCopy Color color; int r, g, b; ColorObject(Color color) - : is_enum(true) - , color(color) - {} + : is_enum(true) + , color(color) + { + } ColorObject(int r, int g, int b) - : is_enum(false) - , r(r) - , g(g) - , b(b) - {} + : is_enum(false) + , r(r) + , g(g) + , b(b) + { + } }; -private: +private: std::string toString(const Color color) const; std::string toString(const ColorObject& color) const; @@ -65,6 +68,8 @@ class SVG : NoCopy bool output_is_html; + void writePathPoint(const Point& p) const; + public: SVG(std::string filename, const AABB aabb, const Point canvas_size = Point(1024, 1024), const ColorObject background = Color::NONE); SVG(std::string filename, const AABB aabb, const double scale, const ColorObject background = Color::NONE); @@ -103,10 +108,10 @@ class SVG : NoCopy /*! * \brief Draws a polyline on the canvas. - * + * * The polyline is the set of line segments between each pair of consecutive * points in the specified vector. - * + * * \param polyline A set of points between which line segments must be * drawn. * \param color The colour of the line segments. If this is not specified, @@ -122,23 +127,23 @@ class SVG : NoCopy /*! * \brief Draws a dashed line on the canvas from point A to point B. - * + * * This is useful in the case where multiple lines may overlap each other. - * + * * \param a The starting endpoint of the line. * \param b The ending endpoint of the line. * \param color The stroke colour of the line. */ - void writeDashedLine(const Point& a,const Point& b, ColorObject color = Color::BLACK) const; + void writeDashedLine(const Point& a, const Point& b, ColorObject color = Color::BLACK) const; template void printf(const char* txt, Args&&... args) const; void writeText(const Point& p, const std::string& txt, const ColorObject color = Color::BLACK, const float font_size = 10) const; - void writePolygons(const Polygons& polys, const ColorObject color = Color::BLACK, const float stroke_width = 1) const; + void writePolygons(const Polygons& polys, const ColorObject color = Color::BLACK, const float stroke_width = 1, bool as_path = false) const; - void writePolygon(ConstPolygonRef poly, const ColorObject color = Color::BLACK, const float stroke_width = 1) const; + void writePolygon(ConstPolygonRef poly, const ColorObject color = Color::BLACK, const float stroke_width = 1, bool as_path = false) const; void writePolylines(const Polygons& polys, const ColorObject color = Color::BLACK, const float stroke_width = 1) const; @@ -187,7 +192,6 @@ class SVG : NoCopy * \param font_size The size of the font to write the coordinates with. */ void writeCoordinateGrid(const coord_t grid_size = MM2INT(1), const Color color = Color::BLACK, const float stroke_width = 0.1, const float font_size = 10) const; - }; template diff --git a/include/utils/gettime.h b/include/utils/gettime.h index 319f5d750c..0f011360ad 100644 --- a/include/utils/gettime.h +++ b/include/utils/gettime.h @@ -4,55 +4,42 @@ #ifndef GETTIME_H #define GETTIME_H -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN 1 -#include -#else -#ifdef USE_CPU_TIME -#include -#endif - -#include -#include -#include -#endif +#include +#include +#include + +#include namespace cura { -static inline double getTime() -{ -#ifdef _WIN32 - return double(GetTickCount()) / 1000.0; -#else // not __WIN32 -#if USE_CPU_TIME // Use cpu usage time if available, otherwise wall clock time - struct rusage usage; -#ifdef DEBUG - int ret = getrusage(RUSAGE_SELF, &usage); - assert(ret == 0); - ((void)ret); -#else - getrusage(RUSAGE_SELF, &usage); -#endif - double user_time = double(usage.ru_utime.tv_sec) + double(usage.ru_utime.tv_usec) / 1000000.0; - double sys_time = double(usage.ru_stime.tv_sec) + double(usage.ru_stime.tv_usec) / 1000000.0; - return user_time + sys_time; -#else // not USE_CPU_TIME - struct timeval tv; - gettimeofday(&tv, nullptr); - return double(tv.tv_sec) + double(tv.tv_usec) / 1000000.0; -#endif // USE_CPU_TIME -#endif // __WIN32 -} class TimeKeeper { +public: + struct RegisteredTime + { + std::string stage; + double duration; + }; + + using RegisteredTimes = std::vector; + private: - double startTime; + spdlog::stopwatch watch; + double start_time; + RegisteredTimes registered_times; public: TimeKeeper(); double restart(); + + void registerTime(const std::string& stage, double threshold = 0.01); + + const RegisteredTimes& getRegisteredTimes() const + { + return registered_times; + } }; } // namespace cura diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index ca089df0c0..285bfa40bd 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -7,6 +7,7 @@ #include // numeric_limits #include #include +#include #include #include @@ -168,15 +169,15 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep total_layers, [&storage, total_layers, this](int layer_nr) { - return &processLayer(storage, layer_nr, total_layers); + return std::make_optional(processLayer(storage, layer_nr, total_layers)); }, - [this, total_layers](LayerPlan* gcode_layer) + [this, total_layers](std::optional result_opt) { - Progress::messageProgress(Progress::Stage::EXPORT, std::max(LayerIndex{ 0 }, gcode_layer->getLayerNr()) + 1, total_layers); - layer_plan_buffer.handle(*gcode_layer, gcode); + const ProcessLayerResult& result = result_opt.value(); + Progress::messageProgressLayer(result.layer_plan->getLayerNr(), total_layers, result.total_elapsed_time, result.stages_times); + layer_plan_buffer.handle(*result.layer_plan, gcode); }); - layer_plan_buffer.flush(); Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper); @@ -945,8 +946,12 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) } } -LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const +FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const { + spdlog::debug("GcodeWriter processing layer {} of {}", layer_nr, total_layers); + TimeKeeper time_keeper; + spdlog::stopwatch timer_total; + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; coord_t layer_thickness = mesh_group_settings.get("layer_height"); coord_t z; @@ -1030,6 +1035,7 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn comb_offset_from_outlines, first_outer_wall_line_width, avoid_distance); + time_keeper.registerTime("Init"); if (include_helper_parts) { @@ -1038,11 +1044,15 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn if (storage.skirt_brim[extruder_nr].size() > 0) { processSkirtBrim(storage, gcode_layer, extruder_nr, layer_nr); + time_keeper.registerTime("Skirt/brim"); } // handle shield(s) first in a layer so that chances are higher that the other nozzle is wiped (for the ooze shield) processOozeShield(storage, gcode_layer); + time_keeper.registerTime("Ooze shield"); + processDraftShield(storage, gcode_layer); + time_keeper.registerTime("Draft shield"); } const size_t support_roof_extruder_nr = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; @@ -1064,10 +1074,12 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn if (extruder_nr != extruder_order.front().extruder_nr || (extruder_order.size() == 1 && layer_nr >= 0) || extruder_nr == 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Prime tower pre"); } if (include_helper_parts && (extruder_nr == support_infill_extruder_nr || extruder_nr == support_roof_extruder_nr || extruder_nr == support_bottom_extruder_nr)) { addSupportToGCode(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Supports"); } if (layer_nr >= 0) { @@ -1087,6 +1099,7 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn { addMeshLayerToGCode(storage, mesh, extruder_nr, mesh_config, gcode_layer); } + time_keeper.registerTime(fmt::format("Mesh {}", mesh_idx)); } } // Always print a prime tower before switching extruder. Unless: @@ -1095,12 +1108,17 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn if (extruder_nr != extruder_order.back().extruder_nr && layer_nr >= 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Prime tower post"); } } gcode_layer.applyModifyPlugin(); + time_keeper.registerTime("Modify plugin"); + gcode_layer.applyBackPressureCompensation(); - return gcode_layer; + time_keeper.registerTime("Back pressure comp."); + + return { &gcode_layer, timer_total.elapsed().count(), time_keeper.getRegisteredTimes() }; } bool FffGcodeWriter::getExtruderNeedPrimeBlobDuringFirstLayer(const SliceDataStorage& storage, const size_t extruder_nr) const @@ -2088,8 +2106,8 @@ bool FffGcodeWriter::processSingleLayerInfill( else // So walls_generated must be true. { std::vector* start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()]; - while (start_paths->empty() || (*start_paths)[0].empty()) // We know for sure (because walls_generated) that one of them is not empty. So randomise until we hit it. - // Should almost always be very quick. + while (start_paths->empty() || (*start_paths)[0].empty()) // We know for sure (because walls_generated) that one of them is not empty. So randomise until we hit + // it. Should almost always be very quick. { start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()]; } @@ -2221,8 +2239,8 @@ bool FffGcodeWriter::partitionInfillBySkinAbove( } else // this layer is the 1st layer above the layer whose infill we're printing { - // add this layer's skin region without subtracting the overlap but still make a gap between this skin region and what has been accumulated so far - // we do this so that these skin region edges will definitely have infill walls below them + // add this layer's skin region without subtracting the overlap but still make a gap between this skin region and what has been accumulated so + // far we do this so that these skin region edges will definitely have infill walls below them // looking from the side, if the combined regions so far look like this... // @@ -2253,8 +2271,8 @@ bool FffGcodeWriter::partitionInfillBySkinAbove( } } - // the shrink/expand here is to remove regions of infill below skin that are narrower than the width of the infill walls otherwise the infill walls could merge and form a - // bump + // the shrink/expand here is to remove regions of infill below skin that are narrower than the width of the infill walls otherwise the infill walls could merge and form + // a bump infill_below_skin = skin_above_combined.intersection(part.infill_area_per_combine_per_density.back().front()).offset(-infill_line_width).offset(infill_line_width); constexpr bool remove_small_holes_from_infill_below_skin = true; diff --git a/src/MeshGroup.cpp b/src/MeshGroup.cpp index 3376e07c7e..84ef4d4923 100644 --- a/src/MeshGroup.cpp +++ b/src/MeshGroup.cpp @@ -3,21 +3,21 @@ #include "MeshGroup.h" -#include "settings/types/Ratio.h" //For the shrinkage percentage and scale factor. -#include "utils/FMatrix4x3.h" //To transform the input meshes for shrinkage compensation and to align in command line mode. -#include "utils/floatpoint.h" //To accept incoming meshes with floating point vertices. -#include "utils/gettime.h" -#include "utils/section_type.h" -#include "utils/string.h" +#include +#include +#include #include #include #include #include -#include -#include -#include +#include "settings/types/Ratio.h" //For the shrinkage percentage and scale factor. +#include "utils/FMatrix4x3.h" //To transform the input meshes for shrinkage compensation and to align in command line mode. +#include "utils/floatpoint.h" //To accept incoming meshes with floating point vertices. +#include "utils/gettime.h" +#include "utils/section_type.h" +#include "utils/string.h" namespace cura { @@ -290,7 +290,7 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMa if (loadMeshSTL(&mesh, filename, transformation)) // Load it! If successful... { meshgroup->meshes.push_back(mesh); - spdlog::info("loading '{}' took {:3} seconds", filename, load_timer.restart()); + spdlog::info("loading '{}' took {:03.3f} seconds", filename, load_timer.restart()); return true; } } diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index 2ae24f4ea6..38cdadd286 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -390,8 +390,8 @@ void PrimeTower::addToGcode( break; case ExtruderPrime::Sparse: - extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, new_extruder_nr, method, { new_extruder_idx }); - addToGcode_optimizedInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); + extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, method, { new_extruder_idx }); + addToGcode_sparseInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); break; case ExtruderPrime::Prime: @@ -401,8 +401,8 @@ void PrimeTower::addToGcode( if (method == PrimeTowerMethod::OPTIMIZED && gcode_layer.getLayerNr() < storage.max_print_height_second_to_last_extruder) { // Whatever happens before and after, use the current extruder to prime all the non-required extruders now - extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, new_extruder_nr, method); - addToGcode_optimizedInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); + extra_primed_extruders_idx = findExtrudersSparseInfill(gcode_layer, required_extruder_prime, method); + addToGcode_sparseInfill(gcode_layer, extra_primed_extruders_idx, new_extruder_nr); } break; } @@ -489,7 +489,7 @@ bool PrimeTower::addToGcode_inset(LayerPlan& gcode_layer, const size_t extruder_ return false; } -void PrimeTower::addToGcode_optimizedInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const +void PrimeTower::addToGcode_sparseInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime_idx, const size_t current_extruder_nr) const { std::vector> extruders_to_prime_idx_grouped; @@ -553,7 +553,6 @@ void PrimeTower::addToGcode_optimizedInfill(LayerPlan& gcode_layer, const std::v std::vector PrimeTower::findExtrudersSparseInfill( LayerPlan& gcode_layer, const std::vector& required_extruder_prime, - const size_t current_extruder_nr, PrimeTowerMethod method, const std::vector& initial_list_idx) const { diff --git a/src/Scene.cpp b/src/Scene.cpp index ff4baf9adc..ac50185b6d 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,11 +1,12 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "Scene.h" + #include #include "Application.h" #include "FffProcessor.h" //To start a slice. -#include "Scene.h" #include "communication/Communication.h" //To flush g-code and layer view when we're done. #include "progress/Progress.h" #include "sliceDataStorage.h" @@ -13,7 +14,9 @@ namespace cura { -Scene::Scene(const size_t num_mesh_groups) : mesh_groups(num_mesh_groups), current_mesh_group(mesh_groups.begin()) +Scene::Scene(const size_t num_mesh_groups) + : mesh_groups(num_mesh_groups) + , current_mesh_group(mesh_groups.begin()) { for (MeshGroup& mesh_group : mesh_groups) { @@ -78,7 +81,7 @@ void Scene::processMeshGroup(MeshGroup& mesh_group) if (empty) { Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup - spdlog::info("Total time elapsed {:3}s.", time_keeper_total.restart()); + spdlog::info("Total time elapsed {:03.3f}s", time_keeper_total.restart()); return; } @@ -94,7 +97,7 @@ void Scene::processMeshGroup(MeshGroup& mesh_group) Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup Application::getInstance().communication->flushGCode(); Application::getInstance().communication->sendOptimizedLayerData(); - spdlog::info("Total time elapsed {:3}s.\n", time_keeper_total.restart()); + spdlog::info("Total time elapsed {:03.3f}s\n", time_keeper_total.restart()); } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index 4b77b449d3..77f9f036c1 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -13,6 +13,7 @@ #include "sliceDataStorage.h" #include "support.h" #include "utils/PolylineStitcher.h" +#include "utils/SVG.h" #include "utils/Simplify.h" //Simplifying the brim/skirt at every inset. namespace cura @@ -121,6 +122,9 @@ void SkirtBrim::generate() const bool has_prime_tower = storage.primeTower.enabled; Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, /*external_polys_only*/ false); + SVG svg("/tmp/machine.svg", storage.machine_size.flatten()); + svg.writePolygons(storage.getMachineBorder(0), SVG::Color::RAINBOW, 1, true); + std::vector allowed_areas_per_extruder(extruder_count); for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { diff --git a/src/progress/Progress.cpp b/src/progress/Progress.cpp index 62687044c6..be6a688813 100644 --- a/src/progress/Progress.cpp +++ b/src/progress/Progress.cpp @@ -1,86 +1,107 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "progress/Progress.h" + #include +#include +#include #include #include "Application.h" //To get the communication channel to send progress through. #include "communication/Communication.h" //To send progress through the communication channel. -#include "progress/Progress.h" #include "utils/gettime.h" namespace cura { - -double Progress::times[] = { - 0.0, // START = 0, - 5.269, // SLICING = 1, - 1.533, // PARTS = 2, - 71.811, // INSET_SKIN = 3 - 51.009, // SUPPORT = 4, - 154.62, // EXPORT = 5, - 0.1 // FINISH = 6 -}; -std::string Progress::names [] = -{ - "start", - "slice", - "layerparts", - "inset+skin", - "support", - "export", - "process" -}; - -double Progress::accumulated_times [N_PROGRESS_STAGES] = {-1}; +std::array Progress::accumulated_times = { -1 }; double Progress::total_timing = -1; +std::optional Progress::first_skipped_layer{}; -float Progress::calcOverallProgress(Stage stage, float stage_progress) +double Progress::calcOverallProgress(Stage stage, double stage_progress) { assert(stage_progress <= 1.0); assert(stage_progress >= 0.0); - return ( accumulated_times[(int)stage] + stage_progress * times[(int)stage] ) / total_timing; + return (accumulated_times.at(static_cast(stage)) + stage_progress * times.at(static_cast(stage))) / total_timing; } - void Progress::init() { double accumulated_time = 0; - for (int stage = 0; stage < N_PROGRESS_STAGES; stage++) + for (size_t stage = 0; stage < N_PROGRESS_STAGES; stage++) { - accumulated_times[(int)stage] = accumulated_time; - accumulated_time += times[(int)stage]; + accumulated_times.at(static_cast(stage)) = accumulated_time; + accumulated_time += times.at(static_cast(stage)); } total_timing = accumulated_time; } void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max) { - float percentage = calcOverallProgress(stage, float(progress_in_stage) / float(progress_in_stage_max)); - Application::getInstance().communication->sendProgress(percentage); - - // logProgress(names[(int)stage].c_str(), progress_in_stage, progress_in_stage_max, percentage); FIXME: use different sink + double percentage = calcOverallProgress(stage, static_cast(progress_in_stage / static_cast(progress_in_stage_max))); + Application::getInstance().communication->sendProgress(static_cast(percentage)); } void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper) { - if (time_keeper) + if (time_keeper != nullptr) { - if ((int)stage > 0) + if (static_cast(stage) > 0) { - spdlog::info("Progress: {} accomplished in {:3}s", names[(int)stage - 1], time_keeper->restart()); + spdlog::info("Progress: {} accomplished in {:03.3f}s", names.at(static_cast(stage) - 1), time_keeper->restart()); } else { time_keeper->restart(); } - - if ((int)stage < (int)Stage::FINISH) + + if (static_cast(stage) < static_cast(Stage::FINISH)) { - spdlog::info("Starting {}...", names[(int)stage]); + spdlog::info("Starting {}...", names.at(static_cast(stage))); + } + } +} + +void Progress::messageProgressLayer(LayerIndex layer_nr, size_t total_layers, double total_time, const TimeKeeper::RegisteredTimes& stages, double skip_threshold) +{ + if (total_time < skip_threshold) + { + if (! first_skipped_layer) + { + first_skipped_layer = layer_nr; + } + } + else + { + if (first_skipped_layer) + { + spdlog::info("Skipped time reporting for layers [{}...{}]", first_skipped_layer.value().value, layer_nr.value); + first_skipped_layer.reset(); + } + + messageProgress(Stage::EXPORT, std::max(layer_nr.value, LayerIndex::value_type(0)) + 1, total_layers); + + spdlog::info("┌ Layer export [{}] accomplished in {:03.3f}s", layer_nr.value, total_time); + + size_t padding = 0; + auto iterator_max_size = std::max_element( + stages.begin(), + stages.end(), + [](const TimeKeeper::RegisteredTime& time1, const TimeKeeper::RegisteredTime& time2) + { + return time1.stage.size() < time2.stage.size(); + }); + if (iterator_max_size != stages.end()) + { + padding = iterator_max_size->stage.size(); + + for (const auto& [index, time] : stages | ranges::views::enumerate) + { + spdlog::info("{}── {}:{} {:03.3f}s", index < stages.size() - 1 ? "├" : "└", time.stage, std::string(padding - time.stage.size(), ' '), time.duration); + } } } } -}// namespace cura \ No newline at end of file +} // namespace cura diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index fbd4a5d1a9..080d62622b 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -560,9 +560,14 @@ Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const Polygons disallowed_areas = mesh_group_settings.get("machine_disallowed_areas"); disallowed_areas = disallowed_areas.unionPolygons(); // union overlapping disallowed areas - for (PolygonRef poly : disallowed_areas) - for (Point& p : poly) - p = Point(machine_size.max.x / 2 + p.X, machine_size.max.y / 2 - p.Y); // apparently the frontend stores the disallowed areas in a different coordinate system + + // The disallowed areas are always expressed in buildplate-centered coordinates + // if (! mesh_group_settings.get("machine_center_is_zero")) + { + for (PolygonRef poly : disallowed_areas) + for (Point& p : poly) + p = Point(machine_size.max.x / 2 + p.X, machine_size.max.y / 2 - p.Y); + } std::vector extruder_is_used = getExtrudersUsed(); diff --git a/src/slicer.cpp b/src/slicer.cpp index 6b1ac7ea4d..2758d133f0 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -3,6 +3,13 @@ #include "slicer.h" +#include // remove_if +#include +#include + +#include +#include + #include "Application.h" #include "Slice.h" #include "plugins/slots.h" @@ -15,13 +22,6 @@ #include "utils/gettime.h" #include "utils/section_type.h" -#include -#include - -#include // remove_if -#include -#include - namespace cura { @@ -819,17 +819,26 @@ Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_c TimeKeeper slice_timer; layers = buildLayersWithHeight(slice_layer_count, slicing_tolerance, initial_layer_thickness, thickness, use_variable_layer_heights, adaptive_layers); - scripta::setAll(layers); + scripta::setAll( + layers, + static_cast(mesh->settings.get("adhesion_type")), + mesh->settings.get("raft_surface_layers"), + mesh->settings.get("raft_surface_thickness"), + mesh->settings.get("raft_interface_layers"), + mesh->settings.get("raft_interface_thickness"), + mesh->settings.get("raft_base_thickness"), + mesh->settings.get("raft_airgap"), + mesh->settings.get("layer_0_z_overlap")); std::vector> zbbox = buildZHeightsForFaces(*mesh); buildSegments(*mesh, zbbox, slicing_tolerance, layers); - spdlog::info("Slice of mesh took {:3} seconds", slice_timer.restart()); + spdlog::info("Slice of mesh took {:03.3f} seconds", slice_timer.restart()); makePolygons(*i_mesh, slicing_tolerance, layers); scripta::log("sliced_polygons", layers, SectionType::NA); - spdlog::info("Make polygons took {:3} seconds", slice_timer.restart()); + spdlog::info("Make polygons took {:03.3f} seconds", slice_timer.restart()); } void Slicer::buildSegments(const Mesh& mesh, const std::vector>& zbbox, const SlicingTolerance& slicing_tolerance, std::vector& layers) diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp index c6959c4810..6726ed092e 100644 --- a/src/utils/SVG.cpp +++ b/src/utils/SVG.cpp @@ -1,12 +1,15 @@ // Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher +#include "utils/SVG.h" + #include +#include +#include #include #include "utils/ExtrusionLine.h" -#include "utils/SVG.h" #include "utils/floatpoint.h" #include "utils/polygon.h" @@ -57,17 +60,34 @@ std::string SVG::toString(const ColorObject& color) const } } +void SVG::writePathPoint(const Point& p) const +{ + FPoint3 transformed = transformF(p); + fprintf(out, "%f,%f", transformed.x, transformed.y); +} + SVG::SVG(std::string filename, AABB aabb, Point canvas_size, ColorObject background) - : SVG(filename, aabb, std::min(double(canvas_size.X - canvas_size.X / 5 * 2) / (aabb.max.X - aabb.min.X), double(canvas_size.Y - canvas_size.Y / 5) / (aabb.max.Y - aabb.min.Y)), canvas_size, background) + : SVG( + filename, + aabb, + std::min(double(canvas_size.X - canvas_size.X / 5 * 2) / (aabb.max.X - aabb.min.X), double(canvas_size.Y - canvas_size.Y / 5) / (aabb.max.Y - aabb.min.Y)), + canvas_size, + background) { } -SVG::SVG(std::string filename, AABB aabb, double scale, ColorObject background) : SVG(filename, aabb, scale, (aabb.max - aabb.min) * scale, background) +SVG::SVG(std::string filename, AABB aabb, double scale, ColorObject background) + : SVG(filename, aabb, scale, (aabb.max - aabb.min) * scale, background) { } -SVG::SVG(std::string filename, AABB aabb, double scale, Point canvas_size, ColorObject background) : aabb(aabb), aabb_size(aabb.max - aabb.min), canvas_size(canvas_size), scale(scale), background(background) +SVG::SVG(std::string filename, AABB aabb, double scale, Point canvas_size, ColorObject background) + : aabb(aabb) + , aabb_size(aabb.max - aabb.min) + , canvas_size(canvas_size) + , scale(scale) + , background(background) { output_is_html = strcmp(filename.c_str() + strlen(filename.c_str()) - 4, "html") == 0; out = fopen(filename.c_str(), "w"); @@ -165,7 +185,12 @@ void SVG::writeAreas(const Polygons& polygons, const ColorObject color, const Co void SVG::writeAreas(ConstPolygonRef polygon, const ColorObject color, const ColorObject outline_color, const float stroke_width) const { - fprintf(out, "& polyline, const ColorObject color } FPoint3 transformed = transformF(polyline[0]); // Element 0 must exist due to the check above. - fprintf(out, "", toString(color).c_str(), fa.x, fa.y, fb.x, fb.y, tip.x, tip.y, b_base.x, b_base.y, a_base.x, a_base.y); + fprintf( + out, + "", + toString(color).c_str(), + fa.x, + fa.y, + fb.x, + fb.y, + tip.x, + tip.y, + b_base.x, + b_base.y, + a_base.x, + a_base.y); } void SVG::writeLineRGB(const Point& from, const Point& to, const int r, const int g, const int b, const float stroke_width) const @@ -259,49 +302,70 @@ void SVG::writeText(const Point& p, const std::string& txt, const ColorObject co fprintf(out, "%s\n", pf.x, pf.y, font_size, toString(color).c_str(), txt.c_str()); } -void SVG::writePolygons(const Polygons& polys, const ColorObject color, const float stroke_width) const +void SVG::writePolygons(const Polygons& polys, const ColorObject color, const float stroke_width, bool as_path) const { for (ConstPolygonRef poly : polys) { - writePolygon(poly, color, stroke_width); + writePolygon(poly, color, stroke_width, as_path); } } -void SVG::writePolygon(ConstPolygonRef poly, const ColorObject color, const float stroke_width) const +void SVG::writePolygon(ConstPolygonRef poly, const ColorObject color, const float stroke_width, bool as_path) const { if (poly.size() == 0) { return; } - int size = poly.size(); - Point p0 = poly.back(); - int i = 0; - for (Point p1 : poly) + + if (as_path) { - if (color.color == Color::RAINBOW) + fprintf(out, " 255) + fprintf(out, " L "); + writePathPoint(p); + } + + fprintf(out, " Z\""); + + fprintf(out, " />\n"); + } + else + { + int size = poly.size(); + Point p0 = poly.back(); + int i = 0; + for (Point p1 : poly) + { + if (color.color == Color::RAINBOW) { - g = 255 * 2 - g; + int g = (i * 255 * 11 / size) % (255 * 2); + if (g > 255) + { + g = 255 * 2 - g; + } + int b = (i * 255 * 5 / size) % (255 * 2); + if (b > 255) + { + b = 255 * 2 - b; + } + writeLineRGB(p0, p1, i * 255 / size, g, b, stroke_width); } - int b = (i * 255 * 5 / size) % (255 * 2); - if (b > 255) + else { - b = 255 * 2 - b; + writeLine(p0, p1, color, stroke_width); } - writeLineRGB(p0, p1, i * 255 / size, g, b, stroke_width); - } - else - { - writeLine(p0, p1, color, stroke_width); + p0 = p1; + i++; } - p0 = p1; - i++; } } - void SVG::writePolylines(const Polygons& polys, const ColorObject color, const float stroke_width) const { for (ConstPolygonRef poly : polys) @@ -378,7 +442,18 @@ void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const fl const FPoint3 end_left = transformF(end_vertex.p + normal(direction_left, std::max(minimum_line_width, end_vertex.w * width_factor))); const FPoint3 end_right = transformF(end_vertex.p + normal(direction_right, std::max(minimum_line_width, end_vertex.w * width_factor))); - fprintf(out, "\n", toString(color).c_str(), start_left.x, start_left.y, start_right.x, start_right.y, end_right.x, end_right.y, end_left.x, end_left.y); + fprintf( + out, + "\n", + toString(color).c_str(), + start_left.x, + start_left.y, + start_right.x, + start_right.y, + end_right.x, + end_right.y, + end_left.x, + end_left.y); start_vertex = end_vertex; // For the next line segment. } diff --git a/src/utils/gettime.cpp b/src/utils/gettime.cpp index bfd665abaa..a7d8303857 100644 --- a/src/utils/gettime.cpp +++ b/src/utils/gettime.cpp @@ -1,21 +1,31 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "utils/gettime.h" +#include + namespace cura { - + TimeKeeper::TimeKeeper() { - restart(); } double TimeKeeper::restart() { - double ret = getTime() - startTime; - startTime = getTime(); + double ret = watch.elapsed().count(); + watch.reset(); return ret; } -}//namespace cura \ No newline at end of file +void TimeKeeper::registerTime(const std::string& stage, double threshold) +{ + double duration = restart(); + if (duration >= threshold) + { + registered_times.emplace_back(RegisteredTime{ stage, duration }); + } +} + +} // namespace cura diff --git a/tests/integration/SlicePhaseTest.cpp b/tests/integration/SlicePhaseTest.cpp index 34e46dc809..75d66899b8 100644 --- a/tests/integration/SlicePhaseTest.cpp +++ b/tests/integration/SlicePhaseTest.cpp @@ -1,6 +1,10 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include + +#include + #include "Application.h" // To set up a slice with settings. #include "Slice.h" // To set up a scene to slice. #include "slicer.h" // Starts the slicing phase that we want to test. @@ -8,8 +12,6 @@ #include "utils/FMatrix4x3.h" // To load STL files. #include "utils/polygon.h" // Creating polygons to compare to sliced layers. #include "utils/polygonUtils.h" // Comparing similarity of polygons. -#include -#include namespace cura { @@ -35,6 +37,13 @@ class SlicePhaseTest : public testing::Test scene.settings.add("slicing_tolerance", "middle"); scene.settings.add("layer_height_0", "0.2"); scene.settings.add("layer_height", "0.1"); + scene.settings.add("layer_0_z_overlap", "0.0"); + scene.settings.add("raft_airgap", "0.0"); + scene.settings.add("raft_base_thickness", "0.2"); + scene.settings.add("raft_interface_thickness", "0.2"); + scene.settings.add("raft_interface_layers", "1"); + scene.settings.add("raft_surface_thickness", "0.2"); + scene.settings.add("raft_surface_layers", "1"); scene.settings.add("magic_mesh_surface_mode", "normal"); scene.settings.add("meshfix_extensive_stitching", "false"); scene.settings.add("meshfix_keep_open_polygons", "false"); @@ -51,6 +60,7 @@ class SlicePhaseTest : public testing::Test scene.settings.add("anti_overhang_mesh", "false"); scene.settings.add("cutting_mesh", "false"); scene.settings.add("infill_mesh", "false"); + scene.settings.add("adhesion_type", "none"); } }; @@ -121,7 +131,8 @@ TEST_F(SlicePhaseTest, Cylinder1000) const FMatrix4x3 transformation; // Path to cylinder1000.stl is relative to CMAKE_CURRENT_SOURCE_DIR/tests. - ASSERT_TRUE(loadMeshIntoMeshGroup(&mesh_group, std::filesystem::path(__FILE__).parent_path().append("resources/cylinder1000.stl").string().c_str(), transformation, scene.settings)); + ASSERT_TRUE( + loadMeshIntoMeshGroup(&mesh_group, std::filesystem::path(__FILE__).parent_path().append("resources/cylinder1000.stl").string().c_str(), transformation, scene.settings)); EXPECT_EQ(mesh_group.meshes.size(), 1); Mesh& cylinder_mesh = mesh_group.meshes[0]; @@ -162,4 +173,4 @@ TEST_F(SlicePhaseTest, Cylinder1000) } } -} // namespace cura \ No newline at end of file +} // namespace cura