#ifndef slic3r_GCodeProcessor_hpp_ #define slic3r_GCodeProcessor_hpp_ #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/CustomGCode.hpp" #include "libslic3r/Extruder.hpp" #include #include #include #include #include #include #include namespace Slic3r { // slice warnings enum strings #define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc" #define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament" #define NOT_SUPPORT_TRADITIONAL_TIMELAPSE "not_support_traditional_timelapse" #define NOT_GENERATE_TIMELAPSE "not_generate_timelapse" #define SMOOTH_TIMELAPSE_WITHOUT_PRIME_TOWER "smooth_timelapse_without_prime_tower" #define LONG_RETRACTION_WHEN_CUT "activate_long_retraction_when_cut" enum class EMoveType : unsigned char { Noop, Retract, Unretract, Seam, Tool_change, Color_change, Pause_Print, Custom_GCode, Travel, Wipe, Extrude, Count }; enum SkipType { stTimelapse, stHeadWrapDetect, stOther, stNone }; const std::unordered_map skip_type_map{ {"timelapse", SkipType::stTimelapse}, {"head_wrap_detect", SkipType::stHeadWrapDetect} }; struct PrintEstimatedStatistics { enum class ETimeMode : unsigned char { Normal, Stealth, Count }; struct Mode { float time; float prepare_time; std::vector>> custom_gcode_times; std::vector> moves_times; std::vector> roles_times; std::vector layers_times; void reset() { time = 0.0f; prepare_time = 0.0f; custom_gcode_times.clear(); custom_gcode_times.shrink_to_fit(); moves_times.clear(); moves_times.shrink_to_fit(); roles_times.clear(); roles_times.shrink_to_fit(); layers_times.clear(); layers_times.shrink_to_fit(); } }; std::vector volumes_per_color_change; std::map model_volumes_per_extruder; std::map wipe_tower_volumes_per_extruder; std::map support_volumes_per_extruder; std::map total_volumes_per_extruder; //BBS: the flush amount of every filament std::map flush_per_filament; std::map> used_filaments_per_role; std::array(ETimeMode::Count)> modes; unsigned int total_filament_changes; unsigned int total_extruder_changes; PrintEstimatedStatistics() { reset(); } void reset() { for (auto m : modes) { m.reset(); } volumes_per_color_change.clear(); volumes_per_color_change.shrink_to_fit(); wipe_tower_volumes_per_extruder.clear(); model_volumes_per_extruder.clear(); support_volumes_per_extruder.clear(); total_volumes_per_extruder.clear(); flush_per_filament.clear(); used_filaments_per_role.clear(); total_filament_changes = 0; total_extruder_changes = 0; } }; struct ConflictResult { std::string _objName1; std::string _objName2; float _height; const void *_obj1; // nullptr means wipe tower const void *_obj2; int layer = -1; ConflictResult(const std::string &objName1, const std::string &objName2, float height, const void *obj1, const void *obj2) : _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2) {} ConflictResult() = default; }; using ConflictResultOpt = std::optional; struct GCodeCheckResult { int error_code = 0; // 0 means succeed, 0001 printable area error, 0010 printable height error std::map>> print_area_error_infos; // printable_area extruder_id to which cannot printed in this extruder std::map>> print_height_error_infos; // printable_height extruder_id to which cannot printed in this extruder void reset() { error_code = 0; print_area_error_infos.clear(); print_height_error_infos.clear(); } }; struct FilamentPrintableResult { std::vector conflict_filament; std::string plate_name; FilamentPrintableResult(){}; FilamentPrintableResult(std::vector &conflict_filament, std::string plate_name) : conflict_filament(conflict_filament), plate_name(plate_name) {} bool has_value(){ return !conflict_filament.empty(); }; }; struct GCodeProcessorResult { struct FilamentSequenceHash { uint64_t operator()(const std::vector& layer_filament) const { uint64_t key = 0; for (auto& f : layer_filament) key |= (uint64_t(1) << f); return key; } }; ConflictResultOpt conflict_result; GCodeCheckResult gcode_check_result; FilamentPrintableResult filament_printable_reuslt; struct SettingsIds { std::string print; std::vector filament; std::string printer; void reset() { print.clear(); filament.clear(); printer.clear(); } }; struct MoveVertex { EMoveType type{ EMoveType::Noop }; ExtrusionRole extrusion_role{ erNone }; //BBS: arc move related data EMovePathType move_path_type{ EMovePathType::Noop_move }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; unsigned int gcode_id{ 0 }; float delta_extruder{ 0.0f }; // mm float feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage float temperature{ 0.0f }; // Celsius degrees float layer_duration{ 0.0f }; // s (layer id before finalize) std::arraytime{ 0.f,0.f }; // prefix sum of time, assigned during finalize() Vec3f position{ Vec3f::Zero() }; // mm Vec3f arc_center_position{ Vec3f::Zero() }; // mm std::vector interpolation_points; // interpolation points of arc for drawing int object_label_id{-1}; float print_z{0.0f}; float volumetric_rate() const { return feedrate * mm3_per_mm; } //BBS: new function to support arc move bool is_arc_move_with_interpolation_points() const { return (move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw) && interpolation_points.size(); } bool is_arc_move() const { return move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw; } }; struct SliceWarning { int level; // 0: normal tips, 1: warning; 2: error std::string msg; // enum string std::string error_code; // error code for studio std::vector params; // extra msg info }; std::string filename; unsigned int id; std::vector moves; // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. std::vector lines_ends; Pointfs printable_area; //BBS: add bed exclude area Pointfs bed_exclude_area; std::vector extruder_areas; std::vector extruder_heights; //BBS: add toolpath_outside bool toolpath_outside; //BBS: add object_label_enabled bool label_object_enabled; //BBS : extra retraction when change filament,experiment func bool long_retraction_when_cut {0}; int timelapse_warning_code {0}; bool support_traditional_timelapse{true}; float printable_height; SettingsIds settings_ids; size_t filaments_count; std::vector extruder_colors; std::vector filament_diameters; std::vector required_nozzle_HRC; std::vector filament_densities; std::vector filament_costs; std::vector filament_vitrification_temperature; std::vector filament_maps; std::vector limit_filament_maps; PrintEstimatedStatistics print_statistics; std::vector custom_gcode_per_print_z; std::vector>> spiral_vase_layers; //BBS std::vector warnings; std::vector nozzle_type; // first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments std::unordered_map, std::vector>,FilamentSequenceHash> layer_filaments; // first key stores `from` filament, second keys stores the `to` filament std::map, int > filament_change_count_map; std::unordered_map skippable_part_time; BedType bed_type = BedType::btCount; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; #endif // ENABLE_GCODE_VIEWER_STATISTICS void reset(); //BBS: add mutex for protection of gcode result mutable std::mutex result_mutex; GCodeProcessorResult& operator=(const GCodeProcessorResult &other) { filename = other.filename; id = other.id; moves = other.moves; lines_ends = other.lines_ends; printable_area = other.printable_area; bed_exclude_area = other.bed_exclude_area; toolpath_outside = other.toolpath_outside; label_object_enabled = other.label_object_enabled; long_retraction_when_cut = other.long_retraction_when_cut; timelapse_warning_code = other.timelapse_warning_code; printable_height = other.printable_height; settings_ids = other.settings_ids; filaments_count = other.filaments_count; extruder_colors = other.extruder_colors; filament_diameters = other.filament_diameters; filament_densities = other.filament_densities; filament_costs = other.filament_costs; print_statistics = other.print_statistics; custom_gcode_per_print_z = other.custom_gcode_per_print_z; spiral_vase_layers = other.spiral_vase_layers; warnings = other.warnings; bed_type = other.bed_type; gcode_check_result = other.gcode_check_result; limit_filament_maps = other.limit_filament_maps; filament_printable_reuslt = other.filament_printable_reuslt; layer_filaments = other.layer_filaments; filament_change_count_map = other.filament_change_count_map; skippable_part_time = other.skippable_part_time; #if ENABLE_GCODE_VIEWER_STATISTICS time = other.time; #endif return *this; } void lock() const { result_mutex.lock(); } void unlock() const { result_mutex.unlock(); } }; namespace ExtruderPreHeating { struct FilamentUsageBlock { int filament_id; unsigned int lower_gcode_id; unsigned int upper_gcode_id; // [lower_gcode_id,upper_gcode_id) uses current filament , upper gcode id will be set after finding next block FilamentUsageBlock(int filament_id_, unsigned int lower_gcode_id_, unsigned int upper_gcode_id_) :filament_id(filament_id_), lower_gcode_id(lower_gcode_id_), upper_gcode_id(upper_gcode_id_) {} }; /** * @brief Describle the usage of a exturder in a section * * The strucutre stores the start and end lines of the sections as well as * the filament used at the beginning and end of the section. * Post extrusion means the final extrusion before switching to the next extruder. * * Simplified GCode Flow: * 1.Extruder Change Block (ext0 switch to ext1) * 2.Extruder Usage Block (use ext1 to print) * 3.Extruder Change Block (ext1 switch to ext0) * 4.Extruder Usage Block (use ext0 to print) * 5.Extruder Change Block (ext0 switch to ex1) * ... * * So the construct of extruder usage block relys on two extruder change block */ struct ExtruderUsageBlcok { int extruder_id = -1; unsigned int start_id = -1; unsigned int end_id = -1; int start_filament = -1; int end_filament = -1; unsigned int post_extrusion_start_id = -1; unsigned int post_extrusion_end_id = -1; void initialize_step_1(int extruder_id_, int start_id_, int start_filament_) { extruder_id = extruder_id_; start_id = start_id_; start_filament = start_filament_; }; void initialize_step_2(int post_extrusion_start_id_) { post_extrusion_start_id = post_extrusion_start_id_; } void initialize_step_3(int end_id_, int end_filament_, int post_extrusion_end_id_) { end_id = end_id_; end_filament = end_filament_; post_extrusion_end_id = post_extrusion_end_id_; } void reset() { *this = ExtruderUsageBlcok(); } ExtruderUsageBlcok() = default; }; } class CommandProcessor { public: using command_handler_t = std::function; private: struct TrieNode { command_handler_t handler{ nullptr }; std::unordered_map> children; bool early_quit{ false }; // stop matching, trigger handle imediately }; public: CommandProcessor(); void register_command(const std::string& str, command_handler_t handler,bool early_quit = false); bool process_comand(std::string_view cmd, const GCodeReader::GCodeLine& line); private: std::unique_ptr root; }; class GCodeProcessor { static const std::vector ReservedTags; static const std::vector CustomTags; public: enum class ETags : unsigned char { Role, Wipe_Start, Wipe_End, Height, Width, Layer_Change, Color_Change, Pause_Print, Custom_Code, First_Line_M73_Placeholder, Last_Line_M73_Placeholder, Estimated_Printing_Time_Placeholder, Total_Layer_Number_Placeholder, Wipe_Tower_Start, Wipe_Tower_End, Used_Filament_Weight_Placeholder, Used_Filament_Volume_Placeholder, Used_Filament_Length_Placeholder, MachineStartGCodeEnd, MachineEndGCodeStart, NozzleChangeStart, NozzleChangeEnd }; enum class CustomETags : unsigned char { FLUSH_START, FLUSH_END, VFLUSH_START, VFLUSH_END, SKIPPABLE_START, SKIPPABLE_END, SKIPPABLE_TYPE }; static const std::string& reserved_tag(ETags tag) { return ReservedTags[static_cast(tag)]; } static const std::string& custom_tags(CustomETags tag) { return CustomTags[static_cast(tag)]; } // checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag) static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag); // checks the given gcode for reserved tags and returns true when finding any // (the first max_count found tags are returned into found_tag) static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); static int get_gcode_last_filament(const std::string &gcode_str); static bool get_last_z_from_gcode(const std::string& gcode_str, double& z); static bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos); static const float Wipe_Width; static const float Wipe_Height; static bool s_IsBBLPrinter; #if ENABLE_GCODE_VIEWER_DATA_CHECKING static const std::string Mm3_Per_Mm_Tag; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING private: using AxisCoords = std::array; using ExtruderColors = std::vector; using ExtruderTemps = std::vector; enum class EUnits : unsigned char { Millimeters, Inches }; enum class EPositioningType : unsigned char { Absolute, Relative }; struct CachedPosition { AxisCoords position; // mm float feedrate; // mm/s void reset(); }; struct CpColor { unsigned char counter; unsigned char current; void reset(); }; public: struct FeedrateProfile { float entry{ 0.0f }; // mm/s float cruise{ 0.0f }; // mm/s float exit{ 0.0f }; // mm/s }; struct Trapezoid { float accelerate_until{ 0.0f }; // mm float decelerate_after{ 0.0f }; // mm float cruise_feedrate{ 0.0f }; // mm/sec float acceleration_time(float entry_feedrate, float acceleration) const; float cruise_time() const; float deceleration_time(float distance, float acceleration) const; float cruise_distance() const; }; struct TimeBlock { struct Flags { bool recalculate{ false }; bool nominal_length{ false }; bool prepare_stage{ false }; }; EMoveType move_type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; SkipType skippable_type{ SkipType::stNone }; unsigned int move_id{ 0 }; // index of the related move vertex, will be assigned duraing gcode process unsigned int g1_line_id{ 0 }; unsigned int layer_id{ 0 }; float distance{ 0.0f }; // mm float acceleration{ 0.0f }; // mm/s^2 float max_entry_speed{ 0.0f }; // mm/s float safe_feedrate{ 0.0f }; // mm/s Flags flags; FeedrateProfile feedrate_profile; Trapezoid trapezoid; // Calculates this block's trapezoid void calculate_trapezoid(); float time() const; }; private: struct TimeMachine { struct State { float feedrate; // mm/s float safe_feedrate; // mm/s //BBS: feedrate of X-Y-Z-E axis. But when the move is G2 and G3, X-Y will be //same value which means feedrate in X-Y plane. AxisCoords axis_feedrate; // mm/s AxisCoords abs_axis_feedrate; // mm/s //BBS: unit vector of enter speed and exit speed in x-y-z space. //For line move, there are same. For arc move, there are different. Vec3f enter_direction; Vec3f exit_direction; void reset(); }; struct CustomGCodeTime { bool needed; float cache; std::vector> times; void reset(); }; struct G1LinesCacheItem { unsigned int id; float elapsed_time; }; bool enabled; float acceleration; // mm/s^2 // hard limit for the acceleration, to which the firmware will clamp. float max_acceleration; // mm/s^2 float retract_acceleration; // mm/s^2 // hard limit for the acceleration, to which the firmware will clamp. float max_retract_acceleration; // mm/s^2 float travel_acceleration; // mm/s^2 // hard limit for the travel acceleration, to which the firmware will clamp. float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s struct StopTime { unsigned int g1_line_id; float elapsed_time; }; std::vector stop_times; std::string line_m73_main_mask; std::string line_m73_stop_mask; State curr; State prev; CustomGCodeTime gcode_time; std::vector blocks; std::vector g1_times_cache; std::array(EMoveType::Count)> moves_time; std::array(ExtrusionRole::erCount)> roles_time; std::vector layers_time; //BBS: prepare stage time before print model, including start gcode time and mostly same with start gcode time float prepare_time; // accept the time block and total time using block_handler_t = std::function; using AdditionalBufferBlock = std::pair; using AdditionalBuffer = std::vector; AdditionalBuffer m_additional_time_buffer; AdditionalBuffer merge_adjacent_addtional_time_blocks(const AdditionalBuffer& buffer); void reset(); /** * @brief Simulates firmware st_synchronize() call * * Adding additional time to the specified extrusion role's time block.The provided block handler * can process the block and the corresponding time (usually assigned to the move of the block). * * @param additional_time Addtional time to calculate * @param target_role Target extrusion role for addtional time.Default is none,means any role is ok. * @param block_handler Handler to set the processing logic for the block and its corresponding time. */ void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t()); /** * @brief Calculates the time for all blocks * * Computes the time for all blocks. The provided block handler can process each block and the * corresponding time (usually assigned to the move of the block). * * @param keep_last_n_blocks The number of last blocks to retain during calculation (default is 0). * @param additional_time Additional time to calculate. * @param target_role Target extrusion role for addtional time.Default is none, means any role is ok. * @param block_handler Handler to set the processing logic for each block and its corresponding time. */ void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f, ExtrusionRole target_role = ExtrusionRole::erNone, block_handler_t block_handler = block_handler_t()); void handle_time_block(const TimeBlock& block, float time, int activate_machine_idx, GCodeProcessorResult& result); }; struct UsedFilaments // filaments per ColorChange { double color_change_cache; std::vector volumes_per_color_change; double model_extrude_cache; std::map model_volumes_per_filament; double wipe_tower_cache; std::mapwipe_tower_volumes_per_filament; double support_volume_cache; std::mapsupport_volumes_per_filament; //BBS: the flush amount of every filament std::map flush_per_filament; double total_volume_cache; std::maptotal_volumes_per_filament; double role_cache; std::map> filaments_per_role; void reset(); void increase_support_caches(double extruded_volume); void increase_model_caches(double extruded_volume); void increase_wipe_tower_caches(double extruded_volume); void process_color_change_cache(); void process_model_cache(GCodeProcessor* processor); void process_wipe_tower_cache(GCodeProcessor* processor); void process_support_cache(GCodeProcessor* processor); void process_total_volume_cache(GCodeProcessor* processor); void update_flush_per_filament(size_t extrude_id, float flush_length); void process_role_cache(GCodeProcessor* processor); void process_caches(GCodeProcessor* processor); friend class GCodeProcessor; }; struct TimeProcessContext { UsedFilaments used_filaments; // stores the accurate filament usage info std::vector filament_lists; std::vector filament_types; std::vector filament_maps; // map each filament to extruder std::vector filament_nozzle_temp; std::vector physical_extruder_map; size_t total_layer_num; std::vector cooling_rate{ 2.f }; // Celsius degree per second std::vector heating_rate{ 2.f }; // Celsius degree per second std::vector pre_cooling_temp{ 0 }; float inject_time_threshold{ 30.f }; // only active pre cooling & heating if time gap is bigger than threshold bool enable_pre_heating{ false }; TimeProcessContext( const UsedFilaments& used_filaments_, const std::vector& filament_lists_, const std::vector& filament_maps_, const std::vector& filament_types_, const std::vector& filament_nozzle_temp_, const std::vector& physical_extruder_map_, const size_t total_layer_num_, const std::vector& cooling_rate_, const std::vector& heating_rate_, const std::vector& pre_cooling_temp_, const float inject_time_threshold_, const bool enable_pre_heating_ ) : used_filaments(used_filaments_), filament_lists(filament_lists_), filament_maps(filament_maps_), filament_types(filament_types_), filament_nozzle_temp(filament_nozzle_temp_), physical_extruder_map(physical_extruder_map_), total_layer_num(total_layer_num_), cooling_rate(cooling_rate_), heating_rate(heating_rate_), pre_cooling_temp(pre_cooling_temp_), enable_pre_heating(enable_pre_heating_), inject_time_threshold(inject_time_threshold_) { } }; struct TimeProcessor { enum InsertLineType { PlaceholderReplace, TimePredict, FilamentChangePredict, ExtruderChangePredict, PreCooling, PreHeating, }; // first key is line id ,second key is content using InsertedLinesMap = std::map>>; struct Planner { // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. // Let's be conservative and plan for newer boards with more memory. static constexpr size_t queue_size = 64; // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added. // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate. static constexpr size_t refresh_threshold = queue_size * 4; }; // extruder_id is currently used to correctly calculate filament load / unload times into the total print time. // This is currently only really used by the MK3 MMU2: // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. bool extruder_unloaded; // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode bool machine_envelope_processing_enabled; MachineEnvelopeConfig machine_limits; // Additional load / unload times for a filament exchange sequence. float filament_load_times; float filament_unload_times; float extruder_change_times; std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; void reset(); // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends, const TimeProcessContext& context); private: void handle_offsets_of_first_process( const std::vector>& offsets, std::vector& moves, std::vector& filament_blocks, std::vector& extruder_blocks, std::vector>& skippable_blocks, unsigned int& machine_start_gcode_end_line_id, unsigned int& machine_end_gcode_start_line_id ); void handle_offsets_of_second_process( const InsertedLinesMap& inserted_operation_lines, std::vector& moves ); }; class PreCoolingInjector { public: struct ExtruderFreeBlock { unsigned int free_lower_gcode_id; unsigned int free_upper_gcode_id; unsigned int partial_free_lower_id; // stores the range of extrusion in wipe tower. Without wipetower, partial free lower_id and upper id will be same as free lower id unsigned int partial_free_upper_id; int last_filament_id; int next_filament_id; int extruder_id; }; void process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines); void build_extruder_free_blocks(const std::vector& filament_usage_blocks, const std::vector& extruder_usage_blocks); PreCoolingInjector( const std::vector& moves_, const std::vector& filament_types_, const std::vector& filament_maps_, const std::vector& filament_nozzle_temps_, const std::vector& physical_extruder_map_, int valid_machine_id_, float inject_time_threshold_, const std::vector & pre_cooling_temp_, const std::vector& cooling_rate_, const std::vector& heating_rate_, const std::vector>& skippable_blocks_, unsigned int machine_start_gcode_end_id_, unsigned int machine_end_gcode_start_id_ ) : moves(moves_), filament_types(filament_types_), filament_maps(filament_maps_), filament_nozzle_temps(filament_nozzle_temps_), physical_extruder_map(physical_extruder_map_), valid_machine_id(valid_machine_id_), inject_time_threshold(inject_time_threshold_), filament_pre_cooling_temps(pre_cooling_temp_), cooling_rate(cooling_rate_), heating_rate(heating_rate_), skippable_blocks(skippable_blocks_), machine_start_gcode_end_id(machine_start_gcode_end_id_), machine_end_gcode_start_id(machine_end_gcode_start_id_) { } private: std::vector m_extruder_free_blocks; const std::vector& moves; const std::vector& filament_types; const std::vector& filament_maps; const std::vector& filament_nozzle_temps; const std::vector& physical_extruder_map; const int valid_machine_id; const float inject_time_threshold; const std::vector& cooling_rate; const std::vector& heating_rate; const std::vector& filament_pre_cooling_temps; // target cooling temp during post extrusion const std::vector>& skippable_blocks; const unsigned int machine_start_gcode_end_id; const unsigned int machine_end_gcode_start_id; void inject_cooling_heating_command( TimeProcessor::InsertedLinesMap& inserted_operation_lines, const ExtruderFreeBlock& free_block, float curr_temp, float target_temp, bool pre_cooling, bool pre_heating ); void build_by_filament_blocks(const std::vector& filament_usage_blocks); void build_by_extruder_blocks(const std::vector& extruder_usage_blocks); }; public: class SeamsDetector { bool m_active{ false }; std::optional m_first_vertex; public: void activate(bool active) { if (m_active != active) { m_active = active; if (m_active) m_first_vertex.reset(); } } std::optional get_first_vertex() const { return m_first_vertex; } void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; } bool is_active() const { return m_active; } bool has_first_vertex() const { return m_first_vertex.has_value(); } }; // Helper class used to fix the z for color change, pause print and // custom gcode markes class OptionsZCorrector { GCodeProcessorResult& m_result; std::optional m_move_id; std::optional m_custom_gcode_per_print_z_id; public: explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) { } void set() { m_move_id = m_result.moves.size() - 1; m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; } void update(float height) { if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) return; const Vec3f position = m_result.moves.back().position; GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); move.position = position; move.height = height; m_result.moves.erase(m_result.moves.begin() + *m_move_id); m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); reset(); } void reset() { m_move_id.reset(); m_custom_gcode_per_print_z_id.reset(); } }; #if ENABLE_GCODE_VIEWER_DATA_CHECKING struct DataChecker { struct Error { float value; float tag_value; ExtrusionRole role; }; std::string type; float threshold{ 0.01f }; float last_tag_value{ 0.0f }; unsigned int count{ 0 }; std::vector errors; DataChecker(const std::string& type, float threshold) : type(type), threshold(threshold) {} void update(float value, ExtrusionRole role) { if (role != erCustom) { ++count; if (last_tag_value != 0.0f) { if (std::abs(value - last_tag_value) / last_tag_value > threshold) errors.push_back({ value, last_tag_value, role }); } } } void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } std::pair get_min() const { float delta_min = FLT_MAX; float perc_min = 0.0f; for (const Error& e : errors) { if (delta_min > e.value - e.tag_value) { delta_min = e.value - e.tag_value; perc_min = 100.0f * delta_min / e.tag_value; } } return { delta_min, perc_min }; } std::pair get_max() const { float delta_max = -FLT_MAX; float perc_max = 0.0f; for (const Error& e : errors) { if (delta_max < e.value - e.tag_value) { delta_max = e.value - e.tag_value; perc_max = 100.0f * delta_max / e.tag_value; } } return { delta_max, perc_max }; } void output() const { if (!errors.empty()) { std::cout << type << ":\n"; std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; auto [min, perc_min] = get_min(); auto [max, perc_max] = get_max(); std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; } } }; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING private: CommandProcessor m_command_processor; GCodeReader m_parser; EUnits m_units; EPositioningType m_global_positioning_type; EPositioningType m_e_local_positioning_type; std::vector m_extruder_offsets; GCodeFlavor m_flavor; std::vector m_nozzle_volume; AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm AxisCoords m_origin; // mm CachedPosition m_cached_position; bool m_wiping; bool m_flushing; // mark a section with real flush bool m_virtual_flushing; // mark a section with virtual flush, only for statistics bool m_wipe_tower; bool m_skippable; SkipType m_skippable_type; int m_object_label_id{-1}; float m_print_z{0.0f}; std::vector m_remaining_volume; std::vector m_filament_lists; std::vector m_filament_nozzle_temp; std::vector m_filament_types; std::vector m_hotend_cooling_rate{ 2.f }; std::vector m_hotend_heating_rate{ 2.f }; std::vector m_filament_pre_cooling_temp{ 0 }; float m_enable_pre_heating{ false }; std::vector m_physical_extruder_map; //BBS: x, y offset for gcode generated double m_x_offset{ 0 }; double m_y_offset{ 0 }; //BBS: arc move related data EMovePathType m_move_path_type{ EMovePathType::Noop_move }; Vec3f m_arc_center{ Vec3f::Zero() }; // mm std::vector m_interpolation_points; unsigned int m_line_id; unsigned int m_last_line_id; float m_feedrate; // mm/s float m_width; // mm float m_height; // mm float m_forced_width; // mm float m_forced_height; // mm float m_mm3_per_mm; float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; std::vector m_filament_maps; std::vector m_last_filament_id; std::vector m_filament_id; unsigned char m_extruder_id; ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; int m_highest_bed_temp; float m_extruded_last_z; float m_first_layer_height; // mm float m_zero_layer_height; // mm bool m_processing_start_custom_gcode; unsigned int m_g1_line_id; unsigned int m_layer_id; CpColor m_cp_color; SeamsDetector m_seams_detector; OptionsZCorrector m_options_z_corrector; size_t m_last_default_color_id; bool m_detect_layer_based_on_tag {false}; int m_seams_count; #if ENABLE_GCODE_VIEWER_STATISTICS std::chrono::time_point m_start_time; #endif // ENABLE_GCODE_VIEWER_STATISTICS enum class EProducer { Unknown, BambuStudio, Slic3rPE, Slic3r, SuperSlicer, Cura, Simplify3D, CraftWare, ideaMaker, KissSlicer }; static const std::vector> Producers; EProducer m_producer; TimeProcessor m_time_processor; UsedFilaments m_used_filaments; GCodeProcessorResult m_result; static unsigned int s_result_id; #if ENABLE_GCODE_VIEWER_DATA_CHECKING DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; DataChecker m_height_compare{ "height", 0.01f }; DataChecker m_width_compare{ "width", 0.01f }; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING public: GCodeProcessor(); // check whether the gcode path meets the filament_map grouping requirements bool check_multi_extruder_gcode_valid(const std::vector &unprintable_areas, const std::vector &printable_heights, const std::vector &filament_map, const std::vector>& unprintable_filament_types ); void apply_config(const PrintConfig& config); void set_filaments(const std::vector&filament_lists) { m_filament_lists=filament_lists;} void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void reset(); const GCodeProcessorResult& get_result() const { return m_result; } GCodeProcessorResult& result() { return m_result; } GCodeProcessorResult&& extract_result() { return std::move(m_result); } // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void process_file(const std::string& filename, std::function cancel_callback = nullptr); // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. void initialize(const std::string& filename); void process_buffer(const std::string& buffer); void finalize(bool post_process); float get_time(PrintEstimatedStatistics::ETimeMode mode) const; float get_prepare_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; //BBS: set offset for gcode writer void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; } // Orca: if true, only change new layer if ETags::Layer_Change occurs // otherwise when we got a lift of z during extrusion, a new layer will be added void detect_layer_based_on_tag(bool enabled) { m_detect_layer_based_on_tag = enabled; } private: void register_commands(); void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); void apply_config_superslicer(const std::string& filename); void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); // Process tags embedded into comments void process_tags(const std::string_view comment, bool producers_enabled); bool process_producers_tags(const std::string_view comment); bool process_bambuslicer_tags(const std::string_view comment); bool process_cura_tags(const std::string_view comment); bool process_simplify3d_tags(const std::string_view comment); bool process_craftware_tags(const std::string_view comment); bool process_ideamaker_tags(const std::string_view comment); bool process_kissslicer_tags(const std::string_view comment); bool detect_producer(const std::string_view comment); // Move void process_G0(const GCodeReader::GCodeLine& line); void process_G1(const GCodeReader::GCodeLine& line); void process_G2_G3(const GCodeReader::GCodeLine& line); void process_VG1(const GCodeReader::GCodeLine& line); // BBS: handle delay command void process_G4(const GCodeReader::GCodeLine& line); // Retract void process_G10(const GCodeReader::GCodeLine& line); // Unretract void process_G11(const GCodeReader::GCodeLine& line); // Set Units to Inches void process_G20(const GCodeReader::GCodeLine& line); // Set Units to Millimeters void process_G21(const GCodeReader::GCodeLine& line); // Firmware controlled Retract void process_G22(const GCodeReader::GCodeLine& line); // Firmware controlled Unretract void process_G23(const GCodeReader::GCodeLine& line); // Move to origin void process_G28(const GCodeReader::GCodeLine& line); // BBS void process_G29(const GCodeReader::GCodeLine& line); // Set to Absolute Positioning void process_G90(const GCodeReader::GCodeLine& line); // Set to Relative Positioning void process_G91(const GCodeReader::GCodeLine& line); // Set Position void process_G92(const GCodeReader::GCodeLine& line); // Sleep or Conditional stop void process_M1(const GCodeReader::GCodeLine& line); // Set extruder to absolute mode void process_M82(const GCodeReader::GCodeLine& line); // Set extruder to relative mode void process_M83(const GCodeReader::GCodeLine& line); // Set extruder temperature void process_M104(const GCodeReader::GCodeLine& line); // Process virtual command of M104, in order to help gcodeviewer work void process_VM104(const GCodeReader::GCodeLine& line); // Process virtual command of M109, in order to help gcodeviewer work void process_VM109(const GCodeReader::GCodeLine& line); // Set fan speed void process_M106(const GCodeReader::GCodeLine& line); // Disable fan void process_M107(const GCodeReader::GCodeLine& line); // Set tool (Sailfish) void process_M108(const GCodeReader::GCodeLine& line); // Set extruder temperature and wait void process_M109(const GCodeReader::GCodeLine& line); // Recall stored home offsets void process_M132(const GCodeReader::GCodeLine& line); // Set tool (MakerWare) void process_M135(const GCodeReader::GCodeLine& line); //BBS: Set bed temperature void process_M140(const GCodeReader::GCodeLine& line); //BBS: wait bed temperature void process_M190(const GCodeReader::GCodeLine& line); //BBS: wait chamber temperature void process_M191(const GCodeReader::GCodeLine& line); // Set max printing acceleration void process_M201(const GCodeReader::GCodeLine& line); // Set maximum feedrate void process_M203(const GCodeReader::GCodeLine& line); // Set default acceleration void process_M204(const GCodeReader::GCodeLine& line); // Advanced settings void process_M205(const GCodeReader::GCodeLine& line); // Klipper SET_VELOCITY_LIMIT void process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& line); // Set extrude factor override percentage void process_M221(const GCodeReader::GCodeLine& line); // BBS: handle delay command. M400 is defined by BBL only void process_M400(const GCodeReader::GCodeLine& line); // Repetier: Store x, y and z position void process_M401(const GCodeReader::GCodeLine& line); // Repetier: Go to stored position void process_M402(const GCodeReader::GCodeLine& line); // Set allowable instantaneous speed change void process_M566(const GCodeReader::GCodeLine& line); // Unload the current filament into the MK3 MMU2 unit at the end of print. void process_M702(const GCodeReader::GCodeLine& line); void process_SYNC(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) void process_T(const GCodeReader::GCodeLine& line); void process_T(const std::string_view command); void process_M1020(const GCodeReader::GCodeLine &line); void process_filament_change(int id); //BBS: different path_type is only used for arc move void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move); void set_extrusion_role(ExtrusionRole role); void set_skippable_type(const std::string_view type); float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const; float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int extruder_id) const; float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const; float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); float get_extruder_change_time(size_t extruder_id); int get_filament_vitrification_temperature(size_t extrude_id); void process_custom_gcode_time(CustomGCode::Type code); void process_filaments(CustomGCode::Type code); // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f, ExtrusionRole target_role =ExtrusionRole::erNone); void update_estimated_times_stats(); //BBS: void update_slice_warnings(); // get current used filament int get_filament_id(bool force_initialize = true) const; // get last used filament in the same extruder with current filament int get_last_filament_id(bool force_initialize = true) const; //get current used extruder int get_extruder_id(bool force_initialize = true)const; }; } /* namespace Slic3r */ #endif /* slic3r_GCodeProcessor_hpp_ */