ENH:Add filament usage stats to GCode files

1.Add accurate filament usage stats to gcode after processing gcode

github:#3090

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: I8eb20c0cf1b9c70f540d4549e2d65b8c79908952
This commit is contained in:
SIMPLE MARK 2024-09-13 14:54:51 +08:00 committed by Lane.Wei
parent 0b5a2e0dcc
commit c7cb968e6d
3 changed files with 127 additions and 47 deletions

View File

@ -1288,7 +1288,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
break;
}
}
m_processor.set_filaments(m_writer.extruders());
m_processor.finalize(true);
// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics);
@ -1323,13 +1323,14 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
// free functions called by GCode::_do_export()
namespace DoExport {
static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled,const std::vector<Extruder>& filaments)
{
silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware)
&& config.silent_mode;
processor.reset();
processor.apply_config(config);
processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
processor.set_filaments(filaments);
}
#if 0
@ -1600,7 +1601,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
PROFILE_FUNC();
// modifies m_silent_time_estimator_enabled
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders());
// resets analyzer's tracking data
m_last_height = 0.f;
m_last_layer_z = 0.f;
@ -1680,6 +1681,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
//BBS: total layer number
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
//BBS: total filament used in mm
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder).c_str());
//BBS: total filament used in cm3
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Volume_Placeholder).c_str());
//BBS: total filament used in g
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Weight_Placeholder).c_str());
//BBS: judge whether support skipping, if yes, list all label_object_id with sorted order here
if (print.num_object_instances() <= g_max_label_object && //Don't support too many objects on one plate
(print.num_object_instances() > 1) && //Don't support skipping single object
@ -2373,11 +2382,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_writer.extruders(),
// Modifies
print.m_print_statistics));
//file.write("\n");
//file.write_format("; total filament weight [g] = %.2lf\n", print.m_print_statistics.total_weight);
//file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
//if (print.m_print_statistics.total_toolchanges > 0)
// file.write_format("; total filament change = %i\n", print.m_print_statistics.total_toolchanges);
bool activate_air_filtration = false;
for (const auto& extruder : m_writer.extruders())

View File

@ -61,7 +61,10 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
"_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER",
"_GP_TOTAL_LAYER_NUMBER_PLACEHOLDER",
" WIPE_TOWER_START",
" WIPE_TOWER_END"
" WIPE_TOWER_END",
"_GP_FILAMENT_USED_WEIGHT_PLACEHOLDER",
"_GP_FILAMENT_USED_VOLUME_PLACEHOLDER",
"_GP_FILAMENT_USED_LENGTH_PLACEHOLDER"
};
const std::string GCodeProcessor::Flush_Start_Tag = " FLUSH_START";
@ -370,7 +373,7 @@ void GCodeProcessor::TimeProcessor::reset()
machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
}
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num)
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, const TimeProcessContext& context)
{
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
if (in.f == nullptr)
@ -448,6 +451,20 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st
auto process_placeholders = [&](std::string& gcode_line) {
unsigned int extra_lines_count = 0;
auto format_filament_used_info = [](const std::string& info, std::map<size_t, double>val_per_extruder) {
auto double_to_fmt_string = [](double num) -> std::string {
char buf[20];
sprintf(buf, "%.2f", num);
return std::string(buf);
};
std::string buf = "; " + info + " : ";
size_t idx = 0;
for (auto item : val_per_extruder)
buf += (idx++ == 0 ? double_to_fmt_string(item.second) : "," + double_to_fmt_string(item.second));
buf += '\n';
return buf;
};
// remove trailing '\n'
auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1);
@ -485,11 +502,12 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st
sprintf(buf, "; estimated printing time (normal mode) = %s\n",
get_time_dhms(machine.time).c_str());
ret += buf;
} else {
}
else {
// BBS estimator
sprintf(buf, "; model printing time: %s; total estimated time: %s\n",
get_time_dhms(machine.time - machine.prepare_time).c_str(),
get_time_dhms(machine.time).c_str());
get_time_dhms(machine.time - machine.prepare_time).c_str(),
get_time_dhms(machine.time).c_str());
ret += buf;
}
}
@ -498,9 +516,48 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st
//BBS: write total layer number
else if (line == reserved_tag(ETags::Total_Layer_Number_Placeholder)) {
char buf[128];
sprintf(buf, "; total layer number: %zd\n", total_layer_num);
sprintf(buf, "; total layer number: %zd\n", context.total_layer_num);
ret += buf;
}
else if (line == reserved_tag(ETags::Used_Filament_Weight_Placeholder)) {
std::map<size_t, double>total_weight_per_extruder;
for (const auto& pair : context.used_filaments.total_volumes_per_extruder) {
auto filament_id = pair.first;
auto volume = pair.second;
auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; });
if (iter == context.filament_lists.end())
continue;
double weight = volume * iter->filament_density() * 0.001;
total_weight_per_extruder[filament_id] += weight;
}
ret += format_filament_used_info("total filament weight [g]", total_weight_per_extruder);
}
else if (line == reserved_tag(ETags::Used_Filament_Volume_Placeholder)) {
std::map<size_t, double>total_volume_per_extruder;
for (const auto& pair : context.used_filaments.total_volumes_per_extruder) {
auto filament_id = pair.first;
auto volume = pair.second;
auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; });
if (iter == context.filament_lists.end())
continue;
total_volume_per_extruder[filament_id] += volume;
}
ret += format_filament_used_info("total filament volume [cm^3]", total_volume_per_extruder);
}
else if (line == reserved_tag(ETags::Used_Filament_Length_Placeholder)) {
std::map<size_t, double>total_length_per_extruder;
for (const auto& pair : context.used_filaments.total_volumes_per_extruder) {
auto filament_id = pair.first;
auto volume = pair.second;
auto iter = std::find_if(context.filament_lists.begin(), context.filament_lists.end(), [filament_id](const Extruder& filament) { return filament.id() == filament_id; });
if (iter == context.filament_lists.end())
continue;
double length = volume / (PI * sqr(0.5 * iter->filament_diameter()));
total_length_per_extruder[filament_id] += length;
}
ret += format_filament_used_info("total filament length [mm]", total_length_per_extruder);
}
}
if (! ret.empty())
@ -1578,7 +1635,8 @@ void GCodeProcessor::finalize(bool post_process)
m_width_compare.output();
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
if (post_process){
m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, m_layer_id);
TimeProcessContext context(m_layer_id,m_filament_lists,m_used_filaments);
m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, context);
}
#if ENABLE_GCODE_VIEWER_STATISTICS
m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start_time).count();

View File

@ -6,6 +6,7 @@
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/CustomGCode.hpp"
#include "libslic3r/Extruder.hpp"
#include <cstdint>
#include <array>
@ -285,6 +286,9 @@ namespace Slic3r {
Total_Layer_Number_Placeholder,
Wipe_Tower_Start,
Wipe_Tower_End,
Used_Filament_Weight_Placeholder,
Used_Filament_Volume_Placeholder,
Used_Filament_Length_Placeholder
};
static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast<unsigned char>(tag)]; }
@ -460,38 +464,6 @@ namespace Slic3r {
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
};
struct TimeProcessor
{
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;
std::array<TimeMachine, static_cast<size_t>(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<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num);
};
struct UsedFilaments // filaments per ColorChange
{
double color_change_cache;
@ -534,6 +506,48 @@ namespace Slic3r {
friend class GCodeProcessor;
};
struct TimeProcessContext
{
size_t total_layer_num;
std::vector<Extruder> filament_lists;
UsedFilaments used_filaments;
TimeProcessContext( size_t total_layer_num_,
const std::vector<Extruder>& filament_lists_,
const UsedFilaments& used_filaments_)
:total_layer_num(total_layer_num_), filament_lists(filament_lists_), used_filaments(used_filaments_) {}
};
struct TimeProcessor
{
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;
std::array<TimeMachine, static_cast<size_t>(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<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, const TimeProcessContext& context);
};
public:
class SeamsDetector
{
@ -677,6 +691,7 @@ namespace Slic3r {
bool m_flushing;
bool m_wipe_tower;
float m_remaining_volume;
std::vector<Extruder> m_filament_lists;
//BBS: x, y offset for gcode generated
double m_x_offset{ 0 };
@ -750,6 +765,9 @@ namespace Slic3r {
GCodeProcessor();
void apply_config(const PrintConfig& config);
void set_filaments(const std::vector<Extruder>&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<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled;