Merge remote-tracking branch 'upstream/main' into libvgcode

# Conflicts:
#	src/libslic3r/GCode/GCodeProcessor.cpp
#	src/libslic3r/GCode/GCodeProcessor.hpp
#	src/slic3r/CMakeLists.txt
#	src/slic3r/GUI/GCodeViewer.cpp
#	src/slic3r/GUI/GCodeViewer.hpp
#	src/slic3r/GUI/GLCanvas3D.cpp
#	src/slic3r/GUI/GLCanvas3D.hpp
#	src/slic3r/GUI/GUI_Preview.cpp
This commit is contained in:
Andrew Sun
2025-11-09 18:48:04 -05:00
2978 changed files with 1208720 additions and 66304 deletions

View File

@@ -773,7 +773,7 @@ static bool need_wipe(const GCode &gcodegen,
const Polyline &result_travel,
const size_t intersection_count)
{
bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().extruder()->id()) > 0.;
bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().filament()->id()) > 0.;
bool wipe_needed = false;
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
@@ -1019,7 +1019,7 @@ static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset_dis)
// remove too small holes from the ex_poly
for (ExPolygon &ex_poly : ex_poly_result) {
for (auto iter = ex_poly.holes.begin(); iter != ex_poly.holes.end();) {
auto out_offset_holes = offset(*iter, scale_(1.0f));
auto out_offset_holes = offset(*iter, scale_(0.1f));
if (out_offset_holes.empty()) {
iter = ex_poly.holes.erase(iter);
} else {

View File

@@ -224,16 +224,16 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP
LinesBucketQueue conflictQueue;
if (wtdptr.has_value()) { // wipe tower at 0 by default
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
ExtrusionLayers wtels;
wtels.type = ExtrusionLayersType::WIPE_TOWER;
for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
ExtrusionLayer el;
el.paths = wtpaths[i];
el.bottom_z = wtpaths[i].front().height * (float) i;
el.layer = nullptr;
wtels.push_back(el);
}
//auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
ExtrusionLayers wtels = wtdptr.value()->getTrueExtrusionLayersFromWipeTower();
//wtels.type = ExtrusionLayersType::WIPE_TOWER;
//for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
// ExtrusionLayer el;
// el.paths = wtpaths[i];
// el.bottom_z = wtpaths[i].front().height * (float) i;
// el.layer = nullptr;
// wtels.push_back(el);
//}
conflictQueue.emplace_back_bucket(std::move(wtels), wtdptr.value(), {wtdptr.value()->plate_origin.x(), wtdptr.value()->plate_origin.y()});
}
for (PrintObject *obj : objs) {

View File

@@ -78,7 +78,7 @@ struct CoolingLine
length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
bool adjustable(bool slowdown_external_perimeters) const {
return (this->type & TYPE_ADJUSTABLE) &&
return (this->type & TYPE_ADJUSTABLE) &&
(! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
this->time < this->time_max;
}
@@ -105,7 +105,7 @@ struct CoolingLine
};
// Calculate the required per extruder time stretches.
struct PerExtruderAdjustments
struct PerExtruderAdjustments
{
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
float elapsed_time_total() const {
@@ -114,7 +114,7 @@ struct PerExtruderAdjustments
time_total += line.time;
return time_total;
}
// Calculate the total elapsed time when slowing down
// Calculate the total elapsed time when slowing down
// to the minimum extrusion feed rate defined for the current material.
float maximum_time_after_slowdown(bool slowdown_external_perimeters) const {
float time_total = 0.f;
@@ -153,7 +153,8 @@ struct PerExtruderAdjustments
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
line.slowdown = true;
line.time = line.time_max;
line.feedrate = line.length / line.time;
if (line.time > 0.f)
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
@@ -169,7 +170,8 @@ struct PerExtruderAdjustments
if (line.adjustable(slowdown_external_perimeters)) {
line.slowdown = true;
line.time = std::min(line.time_max, line.time * factor);
line.feedrate = line.length / line.time;
if (line.time > 0.f)
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
@@ -185,7 +187,7 @@ struct PerExtruderAdjustments
bool adj2 = l2.adjustable();
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
});
for (n_lines_adjustable = 0;
for (n_lines_adjustable = 0;
n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
++ n_lines_adjustable);
time_non_adjustable = 0.f;
@@ -242,7 +244,7 @@ struct PerExtruderAdjustments
// The following two values are set by sort_lines_by_decreasing_feedrate():
// Number of adjustable lines, at the start of lines.
size_t n_lines_adjustable = 0;
// Non-adjustable time of lines starting with n_lines_adjustable.
// Non-adjustable time of lines starting with n_lines_adjustable.
float time_non_adjustable = 0;
// Current total time for this extruder.
float time_total = 0;
@@ -257,7 +259,7 @@ struct PerExtruderAdjustments
// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
// Used by non-proportional slow down.
float new_feedrate_to_reach_time_stretch(
std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,
std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,
float min_feedrate, float time_stretch, size_t max_iter = 20)
{
float new_feedrate = min_feedrate;
@@ -285,7 +287,7 @@ float new_feedrate_to_reach_time_stretch(
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
const CoolingLine &line = (*it)->lines[i];
if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate,
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate,
// which makes the new_feedrate lower than it should be.
// Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate
// are not taken into account.
@@ -361,7 +363,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// Time of any other movements before the first extrusion will be excluded from the layer time.
bool layer_had_extrusion = false;
for (; *line_start != 0; line_start = line_end)
for (; *line_start != 0; line_start = line_end)
{
while (*line_end != '\n' && *line_end != 0)
++ line_end;
@@ -574,7 +576,7 @@ static inline void extruder_range_slow_down_non_proportional(
}
assert(feedrate > 0.f);
// Sort by slow_down_min_speed, maximum speed first.
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->slow_down_min_speed > p2->slow_down_min_speed; });
// Slow down, fast moves first.
for (;;) {
@@ -696,7 +698,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Source G-code for the current layer.
const std::string &gcode,
// ID of the current layer, used to disable fan for the first n layers.
size_t layer_id,
size_t layer_id,
// Total time of this layer after slow down, used to control the fan.
float layer_time,
// Per extruder list of G-code lines and their cool down attributes.

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@ class Print;
#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
@@ -75,7 +76,8 @@ class Print;
std::map<ExtrusionRole, std::pair<double, double>> used_filaments_per_role;
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
unsigned int total_filamentchanges;
unsigned int total_filament_changes;
unsigned int total_extruder_changes;
PrintEstimatedStatistics() { reset(); }
@@ -91,7 +93,8 @@ class Print;
total_volumes_per_extruder.clear();
flush_per_filament.clear();
used_filaments_per_role.clear();
total_filamentchanges = 0;
total_filament_changes = 0;
total_extruder_changes = 0;
}
};
@@ -109,23 +112,46 @@ class Print;
ConflictResult() = default;
};
struct BedMatchResult
using ConflictResultOpt = std::optional<ConflictResult>;
struct GCodeCheckResult
{
bool match;
std::string bed_type_name;
int extruder_id;
BedMatchResult():match(true),bed_type_name(""),extruder_id(-1) {}
BedMatchResult(bool _match,const std::string& _bed_type_name="",int _extruder_id=-1)
:match(_match),bed_type_name(_bed_type_name),extruder_id(_extruder_id)
{}
int error_code = 0; // 0 means succeed, 0b 0001 multi extruder printable area error, 0b 0010 multi extruder printable height error,
// 0b 0100 plate printable area error, 0b 1000 plate printable height error, 0b 10000 wrapping detection area error
std::map<int, std::vector<std::pair<int, int>>> print_area_error_infos; // printable_area extruder_id to <filament_id - object_label_id> which cannot printed in this extruder
std::map<int, std::vector<std::pair<int, int>>> print_height_error_infos; // printable_height extruder_id to <filament_id - object_label_id> which cannot printed in this extruder
void reset() {
error_code = 0;
print_area_error_infos.clear();
print_height_error_infos.clear();
}
};
using ConflictResultOpt = std::optional<ConflictResult>;
struct FilamentPrintableResult
{
std::vector<int> conflict_filament;
std::string plate_name;
FilamentPrintableResult(){};
FilamentPrintableResult(std::vector<int> &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<unsigned int>& layer_filament) const {
uint64_t key = 0;
for (auto& f : layer_filament)
key |= (uint64_t(1) << f);
return key;
}
};
ConflictResultOpt conflict_result;
BedMatchResult bed_match_result;
GCodeCheckResult gcode_check_result;
FilamentPrintableResult filament_printable_reuslt;
struct SettingsIds
{
@@ -161,6 +187,10 @@ class Print;
unsigned int layer_id{ 0 };
bool internal_only{ false };
//BBS
int object_label_id{-1};
float print_z{0.0f};
float volumetric_rate() const { return feedrate * mm3_per_mm; }
float actual_volumetric_rate() const { return actual_feedrate * mm3_per_mm; }
};
@@ -180,6 +210,9 @@ class Print;
Pointfs printable_area;
//BBS: add bed exclude area
Pointfs bed_exclude_area;
Pointfs wrapping_exclude_area;
std::vector<Pointfs> extruder_areas;
std::vector<double> extruder_heights;
//BBS: add toolpath_outside
bool toolpath_outside;
//BBS: add object_label_enabled
@@ -191,7 +224,7 @@ class Print;
float printable_height;
float z_offset;
SettingsIds settings_ids;
size_t extruders_count;
size_t filaments_count;
bool backtrace_enabled;
std::vector<std::string> extruder_colors;
std::vector<float> filament_diameters;
@@ -199,13 +232,20 @@ class Print;
std::vector<float> filament_densities;
std::vector<float> filament_costs;
std::vector<int> filament_vitrification_temperature;
std::vector<int> filament_maps;
std::vector<int> limit_filament_maps;
PrintEstimatedStatistics print_statistics;
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
bool spiral_vase_mode;
//BBS
std::vector<SliceWarning> warnings;
int nozzle_hrc;
NozzleType nozzle_type;
std::vector<NozzleType> nozzle_type;
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
// first key stores `from` filament, second keys stores the `to` filament
std::map<std::pair<int,int>, int > filament_change_count_map;
BedType bed_type = BedType::btCount;
void reset();
@@ -219,13 +259,14 @@ class Print;
lines_ends = other.lines_ends;
printable_area = other.printable_area;
bed_exclude_area = other.bed_exclude_area;
wrapping_exclude_area = other.wrapping_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;
extruders_count = other.extruders_count;
filaments_count = other.filaments_count;
extruder_colors = other.extruder_colors;
filament_diameters = other.filament_diameters;
filament_densities = other.filament_densities;
@@ -235,7 +276,11 @@ class Print;
spiral_vase_mode = other.spiral_vase_mode;
warnings = other.warnings;
bed_type = other.bed_type;
bed_match_result = other.bed_match_result;
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;
#if ENABLE_GCODE_VIEWER_STATISTICS
time = other.time;
#endif
@@ -246,12 +291,32 @@ class Print;
};
class CommandProcessor {
public:
using command_handler_t = std::function<void(const GCodeReader::GCodeLine& line)>;
private:
struct TrieNode {
command_handler_t handler{ nullptr };
std::unordered_map<char, std::unique_ptr<TrieNode>> 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<TrieNode> root;
};
class GCodeProcessor
{
static const std::vector<std::string> Reserved_Tags;
static const std::vector<std::string> Reserved_Tags_compatible;
static const std::string Flush_Start_Tag;
static const std::string Flush_End_Tag;
static const std::string VFlush_Start_Tag;
static const std::string VFlush_End_Tag;
static const std::string External_Purge_Tag;
public:
enum class ETags : unsigned char
@@ -285,6 +350,7 @@ class Print;
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;
@@ -381,6 +447,7 @@ class Print;
private:
friend class ExportLines;
struct TimeMachine
{
struct State
@@ -465,6 +532,48 @@ class Print;
void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
};
struct UsedFilaments // filaments per ColorChange
{
double color_change_cache;
std::vector<double> volumes_per_color_change;
double model_extrude_cache;
std::map<size_t, double> model_volumes_per_filament;
double wipe_tower_cache;
std::map<size_t, double>wipe_tower_volumes_per_filament;
double support_volume_cache;
std::map<size_t, double>support_volumes_per_filament;
//BBS: the flush amount of every filament
std::map<size_t, double> flush_per_filament;
double total_volume_cache;
std::map<size_t, double>total_volumes_per_filament;
double role_cache;
std::map<ExtrusionRole, std::pair<double, double>> 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 TimeProcessor
{
struct Planner
@@ -494,49 +603,6 @@ class Print;
void reset();
};
struct UsedFilaments // filaments per ColorChange
{
double color_change_cache;
std::vector<double> volumes_per_color_change;
double model_extrude_cache;
std::map<size_t, double> model_volumes_per_extruder;
double wipe_tower_cache;
std::map<size_t, double>wipe_tower_volumes_per_extruder;
double support_volume_cache;
std::map<size_t, double>support_volumes_per_extruder;
//BBS: the flush amount of every filament
std::map<size_t, double> flush_per_filament;
double total_volume_cache;
std::map<size_t, double>total_volumes_per_extruder;
double role_cache;
std::map<ExtrusionRole, std::pair<double, double>> 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;
};
public:
class SeamsDetector
{
@@ -665,21 +731,28 @@ class Print;
#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<Vec3f> m_extruder_offsets;
GCodeFlavor m_flavor;
float m_nozzle_volume;
std::vector<float> 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;
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;
float m_remaining_volume;
int m_object_label_id{-1};
float m_print_z{0.0f};
std::vector<float> m_remaining_volume;
ExtruderTemps m_filament_nozzle_temp;
ExtruderTemps m_filament_nozzle_temp_first_layer;
std::vector<int> m_physical_extruder_map;
bool m_manual_filament_change;
//BBS: x, y offset for gcode generated
@@ -698,12 +771,12 @@ class Print;
float m_fan_speed; // percentage
float m_z_offset; // mm
ExtrusionRole m_extrusion_role;
std::vector<int> m_filament_maps;
std::vector<unsigned char> m_last_filament_id;
std::vector<unsigned char> m_filament_id;
unsigned char m_extruder_id;
unsigned char m_last_extruder_id;
ExtruderColors m_extruder_colors;
ExtruderTemps m_extruder_temps;
ExtruderTemps m_extruder_temps_config;
ExtruderTemps m_extruder_temps_first_layer_config;
bool m_is_XL_printer = false;
int m_highest_bed_temp;
float m_extruded_last_z;
@@ -718,6 +791,7 @@ class Print;
size_t m_last_default_color_id;
bool m_detect_layer_based_on_tag {false};
int m_seams_count;
bool m_measure_g29_time {false};
bool m_single_extruder_multi_material;
float m_preheat_time;
int m_preheat_steps;
@@ -750,9 +824,21 @@ class Print;
public:
GCodeProcessor();
void init_filament_maps_and_nozzle_type_when_import_only_gcode();
// check whether the gcode path meets the filament_map grouping requirements
bool check_multi_extruder_gcode_valid(const int extruder_size,
const Pointfs plate_printable_area,
const double plate_printable_height,
const Pointfs wrapping_exclude_area,
const std::vector<Polygons> &unprintable_areas,
const std::vector<double> &printable_heights,
const std::vector<int> &filament_map,
const std::vector<std::set<int>>& unprintable_filament_types );
void apply_config(const PrintConfig& config);
void set_print(Print* print) { m_print = print; }
DynamicConfig export_config_for_render() const;
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;
@@ -793,6 +879,7 @@ class Print;
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);
@@ -824,6 +911,9 @@ class Print;
// Arc Move
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);
void process_VG1(const GCodeReader::GCodeLine& line);
// BBS: handle delay command
void process_G4(const GCodeReader::GCodeLine& line);
@@ -872,6 +962,12 @@ class Print;
// 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);
@@ -932,9 +1028,17 @@ class Print;
// 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_M622(const GCodeReader::GCodeLine &line);
void process_M623(const GCodeReader::GCodeLine &line);
void process_filament_change(int id);
// post process the file with the given filename to:
// 1) add remaining time lines M73 and update moves' gcode ids accordingly
@@ -948,8 +1052,8 @@ class Print;
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) const;
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) 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;
@@ -960,6 +1064,7 @@ class Print;
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);
@@ -975,6 +1080,13 @@ class Print;
//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 */

View File

@@ -491,21 +491,143 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
// Orca:
// Calculate the absolute difference in volumetric extrusion rate between the start and end point of the line.
// Quantize it to 1mm3/min (0.016mm3/sec).
int delta_volumetric_rate = std::round(fabs(line.volumetric_extrusion_rate_end - line.volumetric_extrusion_rate_start));
int delta_volumetric_rate = std::round(std::max({
fabs(line.volumetric_extrusion_rate_end - line.volumetric_extrusion_rate_start),
// For line with accel-then-decel, we also calc the max difference to the peak
fabs(line.volumetric_extrusion_rate - line.volumetric_extrusion_rate_start),
fabs(line.volumetric_extrusion_rate - line.volumetric_extrusion_rate_end),
}));
// Emit the line with lowered extrusion rates.
// Orca:
// First, check if the change in volumetric extrusion rate is trivial (less than 10mm3/min -> 0.16mm3/sec (5mm/sec speed for a 0.25 mm nozzle).
// Or if the line size is equal in length with the smallest segment.
// If so, then emit the line as a single extrusion, i.e. dont split into segments.
if ( nSegments == 1 || delta_volumetric_rate < 10) {
constexpr int NON_TRIVIAL_RATE_DELTA = 10;
if (nSegments == 1 || delta_volumetric_rate < NON_TRIVIAL_RATE_DELTA) {
push_line_to_output(line_idx, line.feedrate() * line.volumetric_correction_avg(), comment);
} else // The line needs to be split the line into segments and apply extrusion rate smoothing
{
bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
const float original_feedrate = line.feedrate();
// Update the initial and final feed rate values.
line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate;
line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate;
// Handle special case where both start & end extrusion rates are smaller than the original extrusion rate,
// which means we need to do an accel-then-decel movements to achieve potentially max print speed
if (line.volumetric_extrusion_rate > line.volumetric_extrusion_rate_start &&
line.volumetric_extrusion_rate > line.volumetric_extrusion_rate_end) {
// total extrusion amount of the original line
const double original_extrusion = (double) l * line.volumetric_extrusion_rate / original_feedrate;
// make sure there is a steady segment that's no shorter than `m_max_segment_length`
const double min_steady_extrusion = original_extrusion * m_max_segment_length / l;
const double max_sloped_extrusion = original_extrusion - min_steady_extrusion;
assert(max_sloped_extrusion > 0);
// Calculate the maximum possible peak extrusion rate
// amount of extrusion if accelerate from volumetric_extrusion_rate_start to volumetric_extrusion_rate then
// decelerate from volumetric_extrusion_rate to volumetric_extrusion_rate_end with max slope rate
const auto pow2 = [](const double x) { return x * x; };
const double e_2 = pow2(line.volumetric_extrusion_rate);
const double e0_2 = pow2(line.volumetric_extrusion_rate_start);
const double e1_2 = pow2(line.volumetric_extrusion_rate_end);
const double sp = line.max_volumetric_extrusion_rate_slope_positive;
const double sn = line.max_volumetric_extrusion_rate_slope_negative;
const double sloped_extrusion = (e_2 - e0_2) / 2 / sp + (e_2 - e1_2) / 2 / sn;
double target_max_extrusion_rate = line.volumetric_extrusion_rate;
if (sloped_extrusion > max_sloped_extrusion) {
// We don't have enough time to accel to max possible extrusion rate
// now we calculate the actual possible value
target_max_extrusion_rate = std::sqrt((2 * max_sloped_extrusion * sp * sn + sn * e0_2 + sp * e1_2) / (sp + sn));
}
assert(target_max_extrusion_rate > line.volumetric_extrusion_rate_start);
assert(target_max_extrusion_rate > line.volumetric_extrusion_rate_end);
assert(target_max_extrusion_rate >= line.volumetric_extrusion_rate);
// if the extrusion rate change is trivial, then ignore this algorithm and use the single sloped version instead
delta_volumetric_rate = std::round(std::min({ // important! it's MIN here not max!
fabs(target_max_extrusion_rate - line.volumetric_extrusion_rate_start),
fabs(target_max_extrusion_rate - line.volumetric_extrusion_rate_end),
}));
if (delta_volumetric_rate >= NON_TRIVIAL_RATE_DELTA) {
// we then have the target max feedrate when we reach target_max_extrusion_rate
const double target_max_feedrate = original_feedrate * target_max_extrusion_rate / line.volumetric_extrusion_rate;
assert(target_max_feedrate <= original_feedrate);
// calculate the accel and deccel time & length
const double t_acc = (target_max_extrusion_rate - line.volumetric_extrusion_rate_start) / sp;
const double l_acc = t_acc * (target_max_feedrate + line.pos_start[4]) / 2;
const double t_dec = (target_max_extrusion_rate - line.volumetric_extrusion_rate_end) / sn;
const double l_dec = t_dec * (target_max_feedrate + line.pos_end[4]) / 2;
float pos_end_bak[5];
memcpy(pos_end_bak, line.pos_end, sizeof(float) * 5); // backup the final pos
float pos_start[5];
float pos_end[5];
memcpy(pos_start, line.pos_start, sizeof(float) * 5);
memcpy(pos_end, line.pos_end, sizeof(float) * 5);
// calculate the end pos of the accel slope
float t = l_acc / l;
for (int i = 0; i < 4; ++i) {
pos_end[i] = pos_start[i] + (pos_end_bak[i] - pos_start[i]) * t;
line.pos_provided[i] = true;
}
// emit accel slope in nSegments
nSegments = size_t(ceil(l_acc / m_max_segment_length));
assert(nSegments > 0);
for (size_t i = 1; i <= nSegments; ++i) {
t = float(i) / float(nSegments);
for (size_t j = 0; j < 4; ++j) {
line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t;
line.pos_provided[j] = true;
}
// Interpolate the feed rate at the center of the segment.
push_line_to_output(line_idx, pos_start[4] + (target_max_feedrate - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment);
comment = nullptr;
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
}
// calculate the end pos of the steady segment
t = (l - l_dec) / l;
for (int i = 0; i < 4; ++i) {
line.pos_end[i] = pos_start[i] + (pos_end_bak[i] - pos_start[i]) * t;
}
// emit the steady feed rate segment
push_line_to_output(line_idx, target_max_feedrate, nullptr);
memcpy(line.pos_start, line.pos_end, sizeof(float) * 5);
// calculate the start pos of the decl slope
memcpy(pos_start, line.pos_end, sizeof(float) * 5);
line.pos_start[4] = target_max_feedrate;
pos_start[4] = target_max_feedrate;
// emit deccel slope in nSegments
nSegments = size_t(ceil(l_dec / m_max_segment_length));
assert(nSegments > 0);
for (size_t i = 1; i <= nSegments; ++ i) {
t = float(i) / float(nSegments);
for (size_t j = 0; j < 4; ++ j) {
line.pos_end[j] = pos_start[j] + (pos_end_bak[j] - pos_start[j]) * t;
}
// Interpolate the feed rate at the center of the segment.
push_line_to_output(line_idx, pos_start[4] + (pos_end_bak[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), nullptr);
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
}
// finish the movement by moving to end pos
for (int i = 0; i < 4; ++i) {
line.pos_end[i] = pos_end_bak[i];
}
push_line_to_output(line_idx, pos_end_bak[4], nullptr);
return;
}
}
bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]);
// Limiting volumetric extrusion rate slope for this segment.
float max_volumetric_extrusion_rate_slope = accelerating ? line.max_volumetric_extrusion_rate_slope_positive :
@@ -515,7 +637,7 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
float t_total = line.dist_xyz() / feed_avg;
// Time of the acceleration / deceleration part of the segment, if accelerating / decelerating
// with the maximum volumetric extrusion rate slope.
float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
float t_acc = std::fabs(line.volumetric_extrusion_rate_start - line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
float l_acc = l;
float l_steady = 0.f;
if (t_acc < t_total) {
@@ -590,30 +712,32 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
}
}
void PressureEqualizer::adjust_volumetric_rate(const size_t fist_line_idx, const size_t last_line_idx)
void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, const size_t last_line_idx)
{
// don't bother adjusting volumetric rate if there's no gcode to adjust
if (last_line_idx-fist_line_idx < 2)
if (last_line_idx - first_line_idx < 2)
return;
size_t line_idx = last_line_idx;
if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding())
size_t line_idx = last_line_idx;
if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding())
// Nothing to do, the last move is not extruding.
return;
std::array<float, size_t(ExtrusionRole::erCount)> feedrate_per_extrusion_role{};
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
while (line_idx != fist_line_idx) {
while (line_idx != first_line_idx) {
size_t idx_prev = line_idx - 1;
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev);
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != first_line_idx; --idx_prev);
if (!m_gcode_lines[idx_prev].extruding())
break;
// Don't decelerate before ironing.
if (m_gcode_lines[line_idx].extrusion_role == ExtrusionRole::erIroning) { line_idx = idx_prev;
if (m_gcode_lines[line_idx].extrusion_role == ExtrusionRole::erIroning) {
line_idx = idx_prev;
continue;
}
// Volumetric extrusion rate at the start of the succeding segment.
// Volumetric extrusion rate at the start of the succeeding segment.
float rate_succ = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
// What is the gradient of the extrusion rate between idx_prev and idx?
line_idx = idx_prev;
@@ -630,8 +754,8 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t fist_line_idx, const
rate_end = rate_succ;
// don't alter the flow rate for these extrusion types
// Orca: Limit ERS to external perimeters and overhangs if option selected by user
if (!line.adjustable_flow || line.extrusion_role == ExtrusionRole::erBridgeInfill || line.extrusion_role == ExtrusionRole::erIroning ||
// Orca: Limit ERS to external perimeters and overhangs if option selected by user
(m_extrusion_rate_smoothing_external_perimeter_only && line.extrusion_role != ExtrusionRole::erOverhangPerimeter && line.extrusion_role != ExtrusionRole::erExternalPerimeter)) {
rate_end = line.volumetric_extrusion_rate_end;
} else if (line.volumetric_extrusion_rate_end > rate_end) {
@@ -687,13 +811,13 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t fist_line_idx, const
float rate_start = feedrate_per_extrusion_role[iRole];
// don't alter the flow rate for these extrusion types
// Orca: Limit ERS to external perimeters and overhangs if option selected by user
if (!line.adjustable_flow || line.extrusion_role == ExtrusionRole::erBridgeInfill || line.extrusion_role == ExtrusionRole::erIroning ||
// Orca: Limit ERS to external perimeters and overhangs if option selected by user
(m_extrusion_rate_smoothing_external_perimeter_only && line.extrusion_role != ExtrusionRole::erOverhangPerimeter && line.extrusion_role != ExtrusionRole::erExternalPerimeter)) {
rate_start = line.volumetric_extrusion_rate_start;
} else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start)
rate_start = rate_prec;
if (line.volumetric_extrusion_rate_start > rate_start) {
line.volumetric_extrusion_rate_start = rate_start;
line.max_volumetric_extrusion_rate_slope_positive = rate_slope;

View File

@@ -136,7 +136,6 @@ private:
assert(avg_correction <= 1.00000001f);
return avg_correction;
}
float time_corrected() const { return time() * volumetric_correction_avg(); }
GCodeLineType type;

View File

@@ -38,6 +38,7 @@ struct ThumbnailsParams
bool show_bed;
bool transparent_background;
int plate_id;
bool use_plate_box{true};
};
typedef std::function<ThumbnailsList(const ThumbnailsParams&)> ThumbnailsGeneratorCallback;

View File

@@ -0,0 +1,590 @@
#include "ClipperUtils.hpp"
#include "TimelapsePosPicker.hpp"
#include "Layer.hpp"
constexpr int FILTER_THRESHOLD = 5;
constexpr int MAX_CANDIDATE_SIZE = 5;
namespace Slic3r {
void TimelapsePosPicker::init(const Print* print_, const Point& plate_offset)
{
reset();
m_plate_offset = plate_offset;
print = print_;
m_nozzle_height_to_rod = print_->config().extruder_clearance_height_to_rod;
m_nozzle_clearance_radius = print_->config().extruder_clearance_radius;
if (print_->config().nozzle_diameter.size() > 1 && print_->config().extruder_printable_height.size() > 1) {
m_extruder_height_gap = std::abs(print_->config().extruder_printable_height.values[0] - print_->config().extruder_printable_height.values[1]);
m_liftable_extruder_id = print_->config().extruder_printable_height.values[0] < print_->config().extruder_printable_height.values[1] ? 0 : 1;
}
m_print_seq = print_->config().print_sequence.value;
m_based_on_all_layer = print_->config().timelapse_type == TimelapseType::tlSmooth;
construct_printable_area_by_printer();
}
void TimelapsePosPicker::reset()
{
print = nullptr;
m_bed_polygon.clear();
m_extruder_printable_area.clear();
m_all_layer_pos = std::nullopt;
bbox_cache.clear();
m_print_seq = PrintSequence::ByObject;
m_nozzle_height_to_rod = 0;
m_nozzle_clearance_radius = 0;
m_liftable_extruder_id = std::nullopt;
m_extruder_height_gap = std::nullopt;
m_based_on_all_layer = false;
}
/**
* @brief Retrieves a list of print objects based on the provided optional set of printed objects.
*
* If the optional set of printed objects is provided, it converts the set into a vector.
* Otherwise, it retrieves all objects from the print instance.
*/
std::vector<const PrintObject*> TimelapsePosPicker::get_object_list(const std::optional<std::vector<const PrintObject*>>& printed_objects)
{
std::vector<const PrintObject*> object_list;
if (printed_objects.has_value()) {
object_list = std::vector<const PrintObject*>(printed_objects->begin(), printed_objects->end());
}
else {
object_list = std::vector<const PrintObject*>(print->objects().begin(), print->objects().end());
}
return object_list;
}
/**
* @brief Constructs the printable area based on printer configuration.
*
* This function initializes the bed polygon, excludes specific areas, accounts for wipe towers,
* and calculates the printable area for each extruder.
*/
void TimelapsePosPicker::construct_printable_area_by_printer()
{
auto config = print->config();
size_t extruder_count = config.nozzle_diameter.size();
m_extruder_printable_area.clear();
m_extruder_printable_area.resize(extruder_count);
for (size_t idx = 0; idx < config.printable_area.values.size(); ++idx)
m_bed_polygon.points.emplace_back(coord_t(scale_(config.printable_area.values[idx].x())), coord_t(scale_(config.printable_area.values[idx].y())));
auto bed_bbox = get_extents(m_bed_polygon);
m_plate_height = unscale_(bed_bbox.max.y());
m_plate_width = unscale_(bed_bbox.max.x());
Polygon bed_exclude_area;
for (size_t idx = 0; idx < config.bed_exclude_area.values.size(); ++idx)
bed_exclude_area.points.emplace_back(coord_t(scale_(config.bed_exclude_area.values[idx].x())), coord_t(scale_(config.bed_exclude_area.values[idx].y())));
Point base_wp_pt = print->get_fake_wipe_tower().pos.cast<coord_t>();
base_wp_pt = Point{ scale_(base_wp_pt.x()),scale_(base_wp_pt.y()) };
auto transform_wt_pt = [base_wp_pt](const Point& pt) -> Point {
Point out =pt;
out += base_wp_pt;
return out;
};
auto wt_box = print->wipe_tower_data().bbx;
Polygon wipe_tower_area{
{transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.min.y())})},
{transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.min.y())})},
{transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.max.y())})},
{transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.max.y())})}
};
wipe_tower_area = expand_object_projection(wipe_tower_area, m_print_seq == PrintSequence::ByObject);
for (size_t idx = 0; idx < extruder_count; ++idx) {
ExPolygons printable_area = diff_ex(diff(m_bed_polygon, bed_exclude_area), { wipe_tower_area });
if (idx < config.extruder_printable_area.size()) {
Polygon extruder_printable_area;
for (size_t j = 0; j < config.extruder_printable_area.values[idx].size(); ++j)
extruder_printable_area.points.emplace_back(coord_t(scale_(config.extruder_printable_area.values[idx][j].x())), coord_t(scale_(config.extruder_printable_area.values[idx][j].y())));
printable_area = intersection_ex(printable_area, Polygons{ extruder_printable_area });
}
m_extruder_printable_area[idx] = printable_area;
}
}
/**
* @brief Collects object slice data within a specified height range for a given layer.
*
* @param layer The layer for which slices are being collected.
* @param height_range The height range to consider for collecting slices.
* @param object_list List of print objects to process.
* @param by_object Decides the expand length of polygon
* @return ExPolygons representing the collected slice data.
*/
ExPolygons TimelapsePosPicker::collect_object_slices_data(const Layer* layer, float height_range, const std::vector<const PrintObject*>& object_list, bool by_object)
{
auto range_intersect = [](int left1, int right1, int left2, int right2) {
if (left1 <= left2 && left2 <= right1)
return true;
if (left2 <= left1 && left1 <= right2)
return true;
return false;
};
ExPolygons ret;
float z_target = layer->print_z;
float z_low = height_range < 0 ? layer->print_z + height_range : layer->print_z;
float z_high = height_range < 0 ? layer->print_z : layer->print_z + height_range;
if (z_low <= 0)
return to_expolygons({ m_bed_polygon });
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
auto instance_bbox = get_real_instance_bbox(instance);
bool higher_than_curr_layer = (layer->object() == obj) ? false : instance_bbox.max.z() > z_target;
if(range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)){
ExPolygon expoly;
expoly.contour = {
{scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())},
{scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())},
{scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())},
{scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())}
};
expoly.contour = expand_object_projection(expoly.contour, by_object, higher_than_curr_layer);
ret.emplace_back(std::move(expoly));
}
}
}
ret = union_ex(ret);
return ret;
}
Polygons TimelapsePosPicker::collect_limit_areas_for_camera(const std::vector<const PrintObject*>& object_list)
{
Polygons ret;
for (auto& obj : object_list)
ret.emplace_back(get_limit_area_for_camera(obj));
ret = union_(ret);
return ret;
}
// scaled data
Polygons TimelapsePosPicker::collect_limit_areas_for_rod(const std::vector<const PrintObject*>& object_list, const PosPickCtx& ctx)
{
double rod_limit_height = m_nozzle_height_to_rod + ctx.curr_layer->print_z;
std::vector<const PrintObject*> rod_collision_candidates;
for(auto& obj : object_list){
if(ctx.printed_objects && obj == ctx.printed_objects->back())
continue;
auto bbox = get_real_instance_bbox(obj->instances().front());
if(bbox.max.z() >= rod_limit_height)
rod_collision_candidates.push_back(obj);
}
if (rod_collision_candidates.empty())
return {};
std::vector<BoundingBoxf3> collision_obj_bboxs;
for (auto obj : rod_collision_candidates) {
collision_obj_bboxs.emplace_back(
expand_object_bbox(
get_real_instance_bbox(obj->instances().front()),
m_print_seq == PrintSequence::ByObject
)
);
}
std::sort(collision_obj_bboxs.begin(), collision_obj_bboxs.end(), [&](const auto& lbbox, const auto& rbbox) {
if (lbbox.min.y() == rbbox.min.y())
return lbbox.max.y() < rbbox.max.y();
return lbbox.min.y() < rbbox.min.y();
});
std::vector<std::pair<int,int>> object_y_ranges = {{0,0}};
for(auto& bbox : collision_obj_bboxs){
if( object_y_ranges.back().second >= bbox.min.y())
object_y_ranges.back().second = bbox.max.y();
else
object_y_ranges.emplace_back(bbox.min.y(), bbox.max.y());
}
if (object_y_ranges.back().second < m_plate_height)
object_y_ranges.emplace_back(m_plate_height, m_plate_height);
int lower_y_pos = -1, upper_y_pos =-1;
Point unscaled_curr_pos = {unscale_(ctx.curr_pos.x())-m_plate_offset.x(), unscale_(ctx.curr_pos.y()) - m_plate_offset.y()};
for (size_t idx = 1; idx < object_y_ranges.size(); ++idx) {
if (unscaled_curr_pos.y() >= object_y_ranges[idx - 1].second && unscaled_curr_pos.y() <= object_y_ranges[idx].first) {
lower_y_pos = object_y_ranges[idx - 1].second;
upper_y_pos = object_y_ranges[idx].first;
break;
}
}
if(lower_y_pos == -1 && upper_y_pos == -1)
return { m_bed_polygon };
Polygons ret;
ret.emplace_back(
Polygon{
Point{scale_(0), scale_(0)},
Point{scale_(m_plate_width), scale_(0)},
Point{scale_(m_plate_width), scale_(lower_y_pos)},
Point{scale_(0), scale_(lower_y_pos)}
}
);
ret.emplace_back(
Polygon{
Point{scale_(0), scale_(upper_y_pos)},
Point{scale_(m_plate_width), scale_(upper_y_pos)},
Point{scale_(m_plate_width), scale_(m_plate_height)},
Point{scale_(0), scale_(m_plate_height)}
}
);
return ret;
}
// expand the object expolygon by safe distance, scaled data
Polygon TimelapsePosPicker::expand_object_projection(const Polygon &poly, bool by_object, bool higher_than_curr)
{
float radius = 0;
if (by_object) {
if (higher_than_curr) {
radius = scale_(print->config().extruder_clearance_radius.value);
}else{
radius = scale_(print->config().extruder_clearance_radius.value / 2);
}
}
else
radius = scale_(print->config().extruder_clearance_radius.value / 2);
// the input poly is bounding box, so we get the first offseted polygon is ok
auto ret = offset(poly, radius);
if (ret.empty())
return {};
return ret[0];
}
// unscaled data
BoundingBoxf3 TimelapsePosPicker::expand_object_bbox(const BoundingBoxf3& bbox, bool by_object)
{
float radius = 0;
if (by_object)
radius = print->config().extruder_clearance_radius.value;
else
radius = print->config().extruder_clearance_radius.value / 2;
BoundingBoxf3 ret = bbox;
ret.min.x() -= radius;
ret.min.y() -= radius;
ret.max.x() += radius;
ret.max.y() += radius;
return ret;
}
double TimelapsePosPicker::get_raft_height(const PrintObject* obj)
{
if (!obj || !obj->has_raft())
return 0;
auto slice_params = obj->slicing_parameters();
int base_raft_layers = slice_params.base_raft_layers;
double base_raft_height = slice_params.base_raft_layer_height;
int interface_raft_layers = slice_params.interface_raft_layers;
double interface_raft_height = slice_params.interface_raft_layer_height;
double contact_raft_layer_height = slice_params.contact_raft_layer_height;
double ret = print->config().initial_layer_print_height;
if (base_raft_layers - 1 > 0)
ret += (base_raft_layers - 1) * base_raft_height;
if (interface_raft_layers - 1 > 0)
ret += (interface_raft_layers - 1) * interface_raft_height;
if (obj->config().raft_layers > 1)
ret += contact_raft_layer_height;
return ret + slice_params.gap_raft_object;
}
// get the real instance bounding box, remove the plate offset and add raft height , unscaled data
BoundingBoxf3 TimelapsePosPicker::get_real_instance_bbox(const PrintInstance& instance)
{
auto iter = bbox_cache.find(&instance);
if (iter != bbox_cache.end())
return iter->second;
auto bbox = instance.get_bounding_box();
double raft_height =get_raft_height(instance.print_object);
bbox.max.z() += raft_height;
// remove plate offset
bbox.min.x() -= m_plate_offset.x();
bbox.max.x() -= m_plate_offset.x();
bbox.min.y() -= m_plate_offset.y();
bbox.max.y() -= m_plate_offset.y();
bbox_cache[&instance] = bbox;
return bbox;
}
Polygon TimelapsePosPicker::get_limit_area_for_camera(const PrintObject* obj)
{
if (!obj)
return {};
auto bbox = get_real_instance_bbox(obj->instances().front());
float radius = m_nozzle_clearance_radius / 2;
auto offset_bbox = bbox.inflated(sqrt(2) * radius);
// Constrain the coordinates to the first quadrant.
Polygon ret = {
DefaultCameraPos,
Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.min.y()),0.)},
Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)},
Point{std::max(scale_(offset_bbox.min.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)}
};
return ret;
}
/**
* @brief Selects the nearest position within the given safe areas relative to the current position.
*
* This function determines the closest point in the safe areas to the provided current position.
* If the current position is already inside a safe area, it returns the current position.
* If no safe areas are defined, return default timelapse position.
*
* @param curr_pos The reference point representing the current position.
* @param safe_areas A collection of extended polygons defining the safe areas.
* @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist.
*/
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision)
{
struct CandidatePoint
{
double dist;
Point point;
bool operator<(const CandidatePoint& other) const {
return dist < other.dist;
}
};
if (std::any_of(safe_areas.begin(), safe_areas.end(), [&curr_pos](const ExPolygon& p) { return p.contains(curr_pos); }))
return curr_pos;
if (safe_areas.empty())
return DefaultTimelapsePos;
std::priority_queue<CandidatePoint> max_heap;
double min_distance = std::numeric_limits<double>::max();
Point nearest_point = DefaultTimelapsePos;
for (const auto& expoly : safe_areas) {
Polygons polys = to_polygons(expoly);
for (auto& poly : polys) {
for (size_t idx = 0; idx < poly.points.size(); ++idx) {
Line line(poly.points[idx], poly.points[next_idx_modulo(idx, poly.points)]);
Point candidate;
double dist = line.distance_to_squared(curr_pos, &candidate);
max_heap.push({ dist,candidate });
if (max_heap.size() > MAX_CANDIDATE_SIZE)
max_heap.pop();
}
}
}
std::vector<Point> top_candidates;
while (!max_heap.empty()) {
top_candidates.push_back(max_heap.top().point);
max_heap.pop();
}
std::reverse(top_candidates.begin(), top_candidates.end());
for (auto& p : top_candidates) {
if (!detect_path_collision)
return p;
Polyline path(curr_pos, p);
if (intersection_pl({path}, path_collision_area).empty())
return p;
}
return DefaultTimelapsePos;
}
Point TimelapsePosPicker::pick_pos(const PosPickCtx& ctx)
{
Point res;
if (m_based_on_all_layer)
res = pick_pos_for_all_layer(ctx);
else
res = pick_pos_for_curr_layer(ctx);
return { unscale_(res.x()), unscale_(res.y()) };
}
// get center point of curr object, scaled data
Point TimelapsePosPicker::get_object_center(const PrintObject* obj)
{
if (!obj)
return {};
// in bambu studio, each object only has one instance
const auto& instance = obj->instances().front();
auto instance_bbox = get_real_instance_bbox(instance);
Point min_p{ instance_bbox.min.x(),instance_bbox.min.y() };
Point max_p{ instance_bbox.max.x(),instance_bbox.max.y() };
return { scale_((min_p.x() + max_p.x()) / 2),scale_((min_p.y() + max_p.y()) / 2) };
}
// scaled data
Point TimelapsePosPicker::pick_nearest_object_center(const Point& curr_pos, const std::vector<const PrintObject*>& object_list)
{
if (object_list.empty())
return {};
const PrintObject* ptr = object_list.front();
double distance = std::numeric_limits<double>::max();
for (auto& obj : object_list) {
Point obj_center = get_object_center(obj);
double dist = (obj_center - curr_pos).cast<double>().norm();
if (distance > dist) {
distance = dist;
ptr = obj;
}
}
return get_object_center(ptr);
}
// scaled data
Point TimelapsePosPicker::pick_pos_for_curr_layer(const PosPickCtx& ctx)
{
float height_gap = 0;
if (ctx.curr_extruder_id != ctx.picture_extruder_id) {
if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value())
height_gap = -*m_extruder_height_gap;
}
bool by_object = m_print_seq == PrintSequence::ByObject;
std::vector<const PrintObject*> object_list = get_object_list(ctx.printed_objects);
ExPolygons layer_slices = collect_object_slices_data(ctx.curr_layer, height_gap, object_list, by_object);
Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list);
Polygons rod_limit_areas;
if (by_object) {
rod_limit_areas = collect_limit_areas_for_rod(object_list, ctx);
}
ExPolygons unplacable_area = union_ex(union_ex(layer_slices, camera_limit_areas), rod_limit_areas);
ExPolygons extruder_printable_area = m_extruder_printable_area[ctx.picture_extruder_id];
ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area);
safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD));
Point center_p;
if (by_object && ctx.printed_objects && !ctx.printed_objects->empty())
center_p = get_object_center(ctx.printed_objects->back());
else
center_p = get_objects_center(object_list);
ExPolygons path_collision_area;
if (by_object) {
auto object_without_curr = ctx.printed_objects;
if (object_without_curr && !object_without_curr->empty())
object_without_curr->pop_back();
ExPolygons layer_slices_without_curr = collect_object_slices_data(ctx.curr_layer, height_gap, get_object_list(object_without_curr), by_object);
path_collision_area = union_ex(layer_slices_without_curr, rod_limit_areas);
}
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object);
}
/**
* @brief Calculates the center of multiple objects.
*
* This function computes the average center of all instances of the provided objects.
*
* @param object_list A vector of pointers to PrintObject instances.
* @return Point The average center of all objects.Scaled data
*/
Point TimelapsePosPicker::get_objects_center(const std::vector<const PrintObject*>& object_list)
{
if (object_list.empty())
return Point(0,0);
double sum_x = 0.0;
double sum_y = 0.0;
size_t total_instances = 0;
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
const auto& bbox = get_real_instance_bbox(instance);
Point min_p{ bbox.min.x(),bbox.min.y() };
Point max_p{ bbox.max.x(),bbox.max.y() };
double center_x = (min_p.x() + max_p.x()) / 2.f;
double center_y = (min_p.y() + max_p.y()) / 2.f;
sum_x += center_x;
sum_y += center_y;
total_instances += 1;
}
}
return Point{ coord_t(scale_(sum_x / total_instances)),coord_t(scale_(sum_y / total_instances)) };
}
Point TimelapsePosPicker::pick_pos_for_all_layer(const PosPickCtx& ctx)
{
bool by_object = m_print_seq == PrintSequence::ByObject;
if (by_object)
return DefaultTimelapsePos;
float height_gap = 0;
if (ctx.curr_extruder_id != ctx.picture_extruder_id) {
if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value())
height_gap = *m_extruder_height_gap;
}
if (ctx.curr_layer->print_z < height_gap)
return DefaultTimelapsePos;
if (m_all_layer_pos)
return *m_all_layer_pos;
Polygons object_projections;
auto object_list = get_object_list(std::nullopt);
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
const auto& bbox = get_real_instance_bbox(instance);
Point min_p{ scale_(bbox.min.x()),scale_(bbox.min.y()) };
Point max_p{ scale_(bbox.max.x()),scale_(bbox.max.y()) };
Polygon obj_proj{ { min_p.x(),min_p.y() },
{ max_p.x(),min_p.y() },
{ max_p.x(),max_p.y() },
{ min_p.x(),max_p.y() }
};
object_projections.emplace_back(expand_object_projection(obj_proj, by_object));
}
};
object_projections = union_(object_projections);
Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list);
Polygons unplacable_area = union_(object_projections, camera_limit_areas);
ExPolygons extruder_printable_area;
if (m_extruder_printable_area.size() > 1)
extruder_printable_area = intersection_ex(m_extruder_printable_area[0], m_extruder_printable_area[1]);
else if (m_extruder_printable_area.size() == 1)
extruder_printable_area = m_extruder_printable_area.front();
ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area);
safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD));
Point starting_pos = get_objects_center(object_list);
m_all_layer_pos = pick_pos_internal(starting_pos, safe_area, {}, by_object);
return *m_all_layer_pos;
}
}

View File

@@ -0,0 +1,81 @@
#ifndef TIMELAPSE_POS_PICKER_HPP
#define TIMELAPSE_POS_PICKER_HPP
#include <vector>
#include "libslic3r/Point.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
const Point DefaultTimelapsePos = Point(0, 0);
const Point DefaultCameraPos = Point(0, 0);
class Layer;
class Print;
struct PosPickCtx
{
Point curr_pos;
const Layer* curr_layer;
int picture_extruder_id; // the extruder id to take picture
int curr_extruder_id;
std::optional<std::vector<const PrintObject*>> printed_objects; // printed objects, only have value in by object mode
};
// data are stored without plate offset
class TimelapsePosPicker
{
public:
TimelapsePosPicker() = default;
~TimelapsePosPicker() = default;
Point pick_pos(const PosPickCtx& ctx);
void init(const Print* print, const Point& plate_offset);
void reset();
private:
void construct_printable_area_by_printer();
Point pick_pos_for_curr_layer(const PosPickCtx& ctx);
Point pick_pos_for_all_layer(const PosPickCtx& ctx);
ExPolygons collect_object_slices_data(const Layer* curr_layer, float height_range, const std::vector<const PrintObject*>& object_list,bool by_object);
Polygons collect_limit_areas_for_camera(const std::vector<const PrintObject*>& object_list);
Polygons collect_limit_areas_for_rod(const std::vector<const PrintObject*>& object_list, const PosPickCtx& ctx);
Polygon expand_object_projection(const Polygon &poly, bool by_object, bool higher_than_curr = true);
BoundingBoxf3 expand_object_bbox(const BoundingBoxf3& bbox, bool by_object);
Point pick_nearest_object_center(const Point& curr_pos, const std::vector<const PrintObject*>& object_list);
Point get_objects_center(const std::vector<const PrintObject*>& object_list);
Polygon get_limit_area_for_camera(const PrintObject* obj);
std::vector<const PrintObject*> get_object_list(const std::optional<std::vector<const PrintObject*>>& printed_objects);
double get_raft_height(const PrintObject* obj);
BoundingBoxf3 get_real_instance_bbox(const PrintInstance& instance);
Point get_object_center(const PrintObject* obj);
private:
const Print* print{ nullptr };
std::vector<ExPolygons> m_extruder_printable_area; //scaled data
Polygon m_bed_polygon; //scaled_data
Point m_plate_offset; // unscaled data
int m_plate_height; // unscaled data
int m_plate_width; // unscaled data
PrintSequence m_print_seq;
bool m_based_on_all_layer;
int m_nozzle_height_to_rod;
int m_nozzle_clearance_radius;
std::optional<int> m_liftable_extruder_id;
std::optional<int> m_extruder_height_gap;
std::unordered_map<const PrintInstance*, BoundingBoxf3> bbox_cache;
std::optional<Point> m_all_layer_pos;
};
}
#endif

View File

@@ -0,0 +1,778 @@
#include "ToolOrderUtils.hpp"
#include <queue>
#include <set>
#include <map>
#include <cmath>
#include <boost/multiprecision/cpp_int.hpp>
namespace Slic3r
{
struct MinCostMaxFlow {
public:
struct Edge {
int from, to, capacity, cost, flow;
Edge(int u, int v, int cap, int cst) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
};
std::vector<int> solve();
void add_edge(int from, int to, int capacity, int cost);
bool spfa(int source, int sink);
int get_distance(int idx_in_left, int idx_in_right);
std::vector<std::vector<float>> matrix;
std::vector<int> l_nodes;
std::vector<int> r_nodes;
std::vector<Edge> edges;
std::vector<std::vector<int>> adj;
int total_nodes{ -1 };
int source_id{ -1 };
int sink_id{ -1 };
};
std::vector<int> MinCostMaxFlow::solve()
{
while (spfa(source_id, sink_id));
std::vector<int>matching(l_nodes.size(), MaxFlowGraph::INVALID_ID);
// to get the match info, just traverse the left nodes and
// check the edges with flow > 0 and linked to right nodes
for (int u = 0; u < l_nodes.size(); ++u) {
for (int eid : adj[u]) {
Edge& e = edges[eid];
if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size())
matching[e.from] = r_nodes[e.to - l_nodes.size()];
}
}
return matching;
}
void MinCostMaxFlow::add_edge(int from, int to, int capacity, int cost)
{
adj[from].emplace_back(edges.size());
edges.emplace_back(from, to, capacity, cost);
//also add reverse edge ,set capacity to zero,cost to negative
adj[to].emplace_back(edges.size());
edges.emplace_back(to, from, 0, -cost);
}
bool MinCostMaxFlow::spfa(int source, int sink)
{
std::vector<int>dist(total_nodes, MaxFlowGraph::INF);
std::vector<bool>in_queue(total_nodes, false);
std::vector<int>flow(total_nodes, MaxFlowGraph::INF);
std::vector<int>prev(total_nodes, 0);
std::queue<int>q;
q.push(source);
in_queue[source] = true;
dist[source] = 0;
while (!q.empty()) {
int now_at = q.front();
q.pop();
in_queue[now_at] = false;
for (auto eid : adj[now_at]) //traverse all linked edges
{
Edge& e = edges[eid];
if (e.flow<e.capacity && dist[e.to]>dist[now_at] + e.cost) {
dist[e.to] = dist[now_at] + e.cost;
prev[e.to] = eid;
flow[e.to] = std::min(flow[now_at], e.capacity - e.flow);
if (!in_queue[e.to]) {
q.push(e.to);
in_queue[e.to] = true;
}
}
}
}
if (dist[sink] == MaxFlowGraph::INF)
return false;
int now_at = sink;
while (now_at != source) {
int prev_edge = prev[now_at];
edges[prev_edge].flow += flow[sink];
edges[prev_edge ^ 1].flow -= flow[sink];
now_at = edges[prev_edge].from;
}
return true;
}
int MinCostMaxFlow::get_distance(int idx_in_left, int idx_in_right)
{
if (l_nodes[idx_in_left] == -1) {
return 0;
//TODO: test more here
int sum = 0;
for (int i = 0; i < matrix.size(); ++i)
sum += matrix[i][idx_in_right];
sum /= matrix.size();
return -sum;
}
return matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
}
MaxFlowSolver::MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
const std::vector<int>& u_capacity,
const std::vector<int>& v_capacity)
{
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
l_nodes = u_nodes;
r_nodes = v_nodes;
total_nodes = u_nodes.size() + v_nodes.size() + 2;
source_id = total_nodes - 2;
sink_id = total_nodes - 1;
adj.resize(total_nodes);
// add edge from source to left nodes
for (int idx = 0; idx < l_nodes.size(); ++idx) {
int capacity = u_capacity.empty() ? 1 : u_capacity[idx];
add_edge(source_id, idx, capacity);
}
// add edge from right nodes to sink node
for (int idx = 0; idx < r_nodes.size(); ++idx) {
int capacity = v_capacity.empty() ? 1 : v_capacity[idx];
add_edge(l_nodes.size() + idx, sink_id, capacity);
}
// add edge from left nodes to right nodes
for (int i = 0; i < l_nodes.size(); ++i) {
int from_idx = i;
// process link limits , i can only link to uv_link_limits
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
for (auto r_id : iter->second)
add_edge(from_idx, l_nodes.size() + r_id, 1);
continue;
}
// process unlink limits
std::optional<std::vector<int>> unlink_limits;
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
unlink_limits = iter->second;
for (int j = 0; j < r_nodes.size(); ++j) {
// check whether i can link to j
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
continue;
add_edge(from_idx, l_nodes.size() + j, 1);
}
}
}
void MaxFlowSolver::add_edge(int from, int to, int capacity)
{
adj[from].emplace_back(edges.size());
edges.emplace_back(from, to, capacity);
//also add reverse edge ,set capacity to zero
adj[to].emplace_back(edges.size());
edges.emplace_back(to, from, 0);
}
std::vector<int> MaxFlowSolver::solve() {
std::vector<int> augment;
std::vector<int> previous(total_nodes, 0);
while (1) {
std::vector<int>(total_nodes, 0).swap(augment);
std::queue<int> travel;
travel.push(source_id);
augment[source_id] = MaxFlowGraph::INF;
while (!travel.empty()) {
int from = travel.front();
travel.pop();
// traverse all linked edges
for (int i = 0; i < adj[from].size(); ++i) {
int eid = adj[from][i];
Edge& tmp = edges[eid];
if (augment[tmp.to] == 0 && tmp.capacity > tmp.flow) {
previous[tmp.to] = eid;
augment[tmp.to] = std::min(augment[from], tmp.capacity - tmp.flow);
travel.push(tmp.to);
}
}
// already find an extend path, stop and do update
if (augment[sink_id] != 0)
break;
}
// no longer have extend path
if (augment[sink_id] == 0)
break;
for (int i = sink_id; i != source_id; i = edges[previous[i]].from) {
edges[previous[i]].flow += augment[sink_id];
edges[previous[i] ^ 1].flow -= augment[sink_id];
}
}
std::vector<int> matching(l_nodes.size(), MaxFlowGraph::INVALID_ID);
// to get the match info, just traverse the left nodes and
// check the edge with flow > 0 and linked to right nodes
for (int u = 0; u < l_nodes.size(); ++u) {
for (int eid : adj[u]) {
Edge& e = edges[eid];
if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size())
matching[e.from] = r_nodes[e.to - l_nodes.size()];
}
}
return matching;
}
GeneralMinCostSolver::~GeneralMinCostSolver()
{
}
GeneralMinCostSolver::GeneralMinCostSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes)
{
m_solver = std::make_unique<MinCostMaxFlow>();
m_solver->matrix = matrix_;;
m_solver->l_nodes = u_nodes;
m_solver->r_nodes = v_nodes;
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
m_solver->source_id =m_solver->total_nodes - 2;
m_solver->sink_id = m_solver->total_nodes - 1;
m_solver->adj.resize(m_solver->total_nodes);
// add edge from source to left nodes,cost to 0
for (int i = 0; i < m_solver->l_nodes.size(); ++i)
m_solver->add_edge(m_solver->source_id, i, 1, 0);
// add edge from right nodes to sink,cost to 0
for (int i = 0; i < m_solver->r_nodes.size(); ++i)
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, 1, 0);
// add edge from left node to right nodes
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
int from_idx = i;
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
int to_idx = m_solver->l_nodes.size() + j;
m_solver->add_edge(from_idx, to_idx, 1, m_solver->get_distance(i, j));
}
}
}
std::vector<int> GeneralMinCostSolver::solve() {
return m_solver->solve();
}
MinFlushFlowSolver::~MinFlushFlowSolver()
{
}
MinFlushFlowSolver::MinFlushFlowSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
const std::vector<int>& u_capacity,
const std::vector<int>& v_capacity)
{
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
m_solver = std::make_unique<MinCostMaxFlow>();
m_solver->matrix = matrix_;;
m_solver->l_nodes = u_nodes;
m_solver->r_nodes = v_nodes;
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
m_solver->source_id =m_solver->total_nodes - 2;
m_solver->sink_id = m_solver->total_nodes - 1;
m_solver->adj.resize(m_solver->total_nodes);
// add edge from source to left nodes,cost to 0
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
int capacity = u_capacity.empty() ? 1 : u_capacity[i];
m_solver->add_edge(m_solver->source_id, i, capacity, 0);
}
// add edge from right nodes to sink,cost to 0
for (int i = 0; i < m_solver->r_nodes.size(); ++i) {
int capacity = v_capacity.empty() ? 1 : v_capacity[i];
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, capacity, 0);
}
// add edge from left node to right nodes
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
int from_idx = i;
// process link limits, i can only link to link_limits
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
for (auto r_id : iter->second)
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + r_id, 1, m_solver->get_distance(i, r_id));
continue;
}
// process unlink limits, check whether i can link to j
std::optional<std::vector<int>> unlink_limits;
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
unlink_limits = iter->second;
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
continue;
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j));
}
}
}
std::vector<int> MinFlushFlowSolver::solve() {
return m_solver->solve();
}
MatchModeGroupSolver::~MatchModeGroupSolver()
{
}
MatchModeGroupSolver::MatchModeGroupSolver(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes, const std::vector<int>& v_capacity, const std::unordered_map<int, std::vector<int>>& uv_unlink_limits)
{
assert(v_nodes.size() == v_capacity.size());
m_solver = std::make_unique<MinCostMaxFlow>();
m_solver->matrix = matrix_;;
m_solver->l_nodes = u_nodes;
m_solver->r_nodes = v_nodes;
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
m_solver->source_id = m_solver->total_nodes - 2;
m_solver->sink_id = m_solver->total_nodes - 1;
m_solver->adj.resize(m_solver->total_nodes);
// add edge from source to left nodes,cost to 0
for (int i = 0; i < m_solver->l_nodes.size(); ++i)
m_solver->add_edge(m_solver->source_id, i, 1, 0);
// add edge from right nodes to sink,cost to 0
for (int i = 0; i < m_solver->r_nodes.size(); ++i)
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, v_capacity[i], 0);
// add edge from left node to right nodes
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
int from_idx = i;
// process unlink limits, check whether i can link to j
std::optional<std::vector<int>> unlink_limits;
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
unlink_limits = iter->second;
for (int j = 0; j < m_solver->r_nodes.size(); ++j) {
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
continue;
m_solver->add_edge(from_idx, m_solver->l_nodes.size() + j, 1, m_solver->get_distance(i, j));
}
}
}
std::vector<int> MatchModeGroupSolver::solve() {
return m_solver->solve();
}
//solve the problem by searching the least flush of current filament
static std::vector<unsigned int> solve_extruder_order_with_greedy(const std::vector<std::vector<float>>& wipe_volumes,
const std::vector<unsigned int> curr_layer_extruders,
const std::optional<unsigned int>& start_extruder_id,
float* min_cost)
{
float cost = 0;
std::vector<unsigned int> best_seq;
std::vector<bool>is_visited(curr_layer_extruders.size(), false);
std::optional<unsigned int>prev_filament = start_extruder_id;
int idx = curr_layer_extruders.size();
while (idx > 0) {
if (!prev_filament) {
auto iter = std::find_if(is_visited.begin(), is_visited.end(), [](auto item) {return item == 0; });
assert(iter != is_visited.end());
prev_filament = curr_layer_extruders[iter - is_visited.begin()];
}
int target_idx = -1;
int target_cost = std::numeric_limits<int>::max();
for (size_t k = 0; k < is_visited.size(); ++k) {
if (!is_visited[k]) {
if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost ||
(wipe_volumes[*prev_filament][curr_layer_extruders[k]] == target_cost && prev_filament == curr_layer_extruders[k])) {
target_idx = k;
target_cost = wipe_volumes[*prev_filament][curr_layer_extruders[k]];
}
}
}
assert(target_idx != -1);
cost += target_cost;
best_seq.emplace_back(curr_layer_extruders[target_idx]);
prev_filament = curr_layer_extruders[target_idx];
is_visited[target_idx] = true;
idx -= 1;
}
if (min_cost)
*min_cost = cost;
return best_seq;
}
//solve the problem by forcasting one layer
static std::vector<unsigned int> solve_extruder_order_with_forcast(const std::vector<std::vector<float>>& wipe_volumes,
std::vector<unsigned int> curr_layer_extruders,
std::vector<unsigned int> next_layer_extruders,
const std::optional<unsigned int>& start_extruder_id,
float* min_cost)
{
std::sort(curr_layer_extruders.begin(), curr_layer_extruders.end());
std::sort(next_layer_extruders.begin(), next_layer_extruders.end());
float best_cost = std::numeric_limits<float>::max();
int best_change = std::numeric_limits<int>::max(); // add filament change check in case flush volume between different filament is 0
std::vector<unsigned int>best_seq;
auto get_filament_change_count = [](const std::vector<unsigned int>& curr_seq, const std::vector<unsigned int>& next_seq,const std::optional<unsigned int>& start_extruder_id) {
int count = 0;
auto prev_extruder_id = start_extruder_id;
for (auto seq : { curr_seq,next_seq }) {
for (auto eid : seq) {
if (prev_extruder_id && prev_extruder_id != eid) {
count += 1;
}
prev_extruder_id = eid;
}
}
return count;
};
do {
std::optional<unsigned int>prev_extruder_1 = start_extruder_id;
float curr_layer_cost = 0;
for (size_t idx = 0; idx < curr_layer_extruders.size(); ++idx) {
if (prev_extruder_1)
curr_layer_cost += wipe_volumes[*prev_extruder_1][curr_layer_extruders[idx]];
prev_extruder_1 = curr_layer_extruders[idx];
}
if (curr_layer_cost > best_cost)
continue;
do {
std::optional<unsigned int>prev_extruder_2 = prev_extruder_1;
float total_cost = curr_layer_cost;
int total_change = get_filament_change_count(curr_layer_extruders, next_layer_extruders, start_extruder_id);
for (size_t idx = 0; idx < next_layer_extruders.size(); ++idx) {
if (prev_extruder_2)
total_cost += wipe_volumes[*prev_extruder_2][next_layer_extruders[idx]];
prev_extruder_2 = next_layer_extruders[idx];
}
if (total_cost < best_cost || (total_cost == best_cost && total_change < best_change)) {
best_cost = total_cost;
best_seq = curr_layer_extruders;
best_change = total_change;
}
} while (std::next_permutation(next_layer_extruders.begin(), next_layer_extruders.end()));
} while (std::next_permutation(curr_layer_extruders.begin(), curr_layer_extruders.end()));
if (min_cost) {
float real_cost = 0;
std::optional<unsigned int>prev_extruder = start_extruder_id;
for (size_t idx = 0; idx < best_seq.size(); ++idx) {
if (prev_extruder)
real_cost += wipe_volumes[*prev_extruder][best_seq[idx]];
prev_extruder = best_seq[idx];
}
*min_cost = real_cost;
}
return best_seq;
}
// Shortest hamilton path problem
static std::vector<unsigned int> solve_extruder_order(const std::vector<std::vector<float>>& wipe_volumes,
std::vector<unsigned int> all_extruders,
std::optional<unsigned int> start_extruder_id,
float* min_cost)
{
bool add_start_extruder_flag = false;
if (start_extruder_id) {
auto start_iter = std::find(all_extruders.begin(), all_extruders.end(), start_extruder_id);
if (start_iter == all_extruders.end())
all_extruders.insert(all_extruders.begin(), *start_extruder_id), add_start_extruder_flag = true;
else
std::swap(*all_extruders.begin(), *start_iter);
}
else {
start_extruder_id = all_extruders.front();
}
unsigned int iterations = (1 << all_extruders.size());
unsigned int final_state = iterations - 1;
std::vector<std::vector<float>>cache(iterations, std::vector<float>(all_extruders.size(), 0x7fffffff));
std::vector<std::vector<int>>prev(iterations, std::vector<int>(all_extruders.size(), -1));
cache[1][0] = 0.;
for (unsigned int state = 0; state < iterations; ++state) {
if (state & 1) {
for (unsigned int target = 0; target < all_extruders.size(); ++target) {
if (state >> target & 1) {
for (unsigned int mid_point = 0; mid_point < all_extruders.size(); ++mid_point) {
if (state >> mid_point & 1) {
auto tmp = cache[state - (1 << target)][mid_point] + wipe_volumes[all_extruders[mid_point]][all_extruders[target]];
if (cache[state][target] > tmp) {
cache[state][target] = tmp;
prev[state][target] = mid_point;
}
}
}
}
}
}
}
//get res
float cost = std::numeric_limits<float>::max();
int final_dst = 0;
for (unsigned int dst = 0; dst < all_extruders.size(); ++dst) {
if (all_extruders[dst] != start_extruder_id && cost > cache[final_state][dst]) {
cost = cache[final_state][dst];
if (min_cost)
*min_cost = cost;
final_dst = dst;
}
}
std::vector<unsigned int>path;
unsigned int curr_state = final_state;
int curr_point = final_dst;
while (curr_point != -1) {
path.emplace_back(all_extruders[curr_point]);
auto mid_point = prev[curr_state][curr_point];
curr_state -= (1 << curr_point);
curr_point = mid_point;
};
if (add_start_extruder_flag)
path.pop_back();
std::reverse(path.begin(), path.end());
return path;
}
template<class T>
static std::vector<T> collect_filaments_in_groups(const std::unordered_set<unsigned int>& group, const std::vector<unsigned int>& filament_list) {
std::vector<T>ret;
ret.reserve(group.size());
for (auto& f : filament_list) {
if (auto iter = group.find(f); iter != group.end())
ret.emplace_back(static_cast<T>(f));
}
return ret;
}
// get best filament order of single nozzle
std::vector<unsigned int> get_extruders_order(const std::vector<std::vector<float>>& wipe_volumes,
const std::vector<unsigned int>& curr_layer_extruders,
const std::vector<unsigned int>& next_layer_extruders,
const std::optional<unsigned int>& start_extruder_id,
bool use_forcast,
float* cost)
{
if (curr_layer_extruders.empty()) {
if (cost)
*cost = 0;
return curr_layer_extruders;
}
if (curr_layer_extruders.size() == 1) {
if (cost) {
*cost = 0;
if (start_extruder_id)
*cost = wipe_volumes[*start_extruder_id][curr_layer_extruders[0]];
}
return curr_layer_extruders;
}
if (use_forcast)
return solve_extruder_order_with_forcast(wipe_volumes, curr_layer_extruders, next_layer_extruders, start_extruder_id, cost);
else if (curr_layer_extruders.size() <= 20)
return solve_extruder_order(wipe_volumes, curr_layer_extruders, start_extruder_id, cost);
else
return solve_extruder_order_with_greedy(wipe_volumes, curr_layer_extruders, start_extruder_id, cost);
}
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int>& filament_lists,
const std::vector<int>& filament_maps,
const std::vector<std::vector<unsigned int>>& layer_filaments,
const std::vector<FlushMatrix>& flush_matrix,
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq,
std::vector<std::vector<unsigned int>>* filament_sequences)
{
//only when layer filament num <= 5,we do forcast
constexpr int max_n_with_forcast = 5;
int cost = 0;
std::vector<std::unordered_set<unsigned int>>groups(2); //save the grouped filaments
std::vector<std::vector<std::vector<unsigned int>>> layer_sequences(2); //save the reordered filament sequence by group
std::map<size_t, std::vector<unsigned int>> custom_layer_sequence_map; // save the filament sequences of custom layer
// group the filament
for (int i = 0; i < filament_maps.size(); ++i) {
if (filament_maps[i] == 0)
groups[0].insert(filament_lists[i]);
if (filament_maps[i] == 1)
groups[1].insert(filament_lists[i]);
}
// store custom layer sequence
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
const auto& curr_lf = layer_filaments[layer];
std::vector<int>custom_filament_seq;
if (get_custom_seq && (*get_custom_seq)(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
std::vector<unsigned int> unsign_custom_extruder_seq;
for (int extruder : custom_filament_seq) {
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
auto it = std::find(curr_lf.begin(), curr_lf.end(), unsign_extruder);
if (it != curr_lf.end())
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
}
assert(curr_lf.size() == unsign_custom_extruder_seq.size());
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
}
}
using uint128_t = boost::multiprecision::uint128_t;
auto extruders_to_hash_key = [](const std::vector<unsigned int>& curr_layer_extruders,
const std::vector<unsigned int>& next_layer_extruders,
const std::optional<unsigned int>& prev_extruder,
bool use_forcast)->uint128_t
{
uint128_t hash_key = 0;
//31-0 bit define current layer extruder,63-32 bit define next layer extruder,95~64 define prev extruder
if (prev_extruder)
hash_key |= (uint128_t(1) << (64 + *prev_extruder));
if (use_forcast) {
for (auto item : next_layer_extruders)
hash_key |= (uint128_t(1) << (32 + item));
}
for (auto item : curr_layer_extruders)
hash_key |= (uint128_t(1) << item);
return hash_key;
};
// get best layer sequence by group
for (size_t idx = 0; idx < groups.size(); ++idx) {
// case with one group
if (groups[idx].empty())
continue;
std::optional<unsigned int>current_extruder_id;
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
const auto& curr_lf = layer_filaments[layer];
if (auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()) {
auto sequence_in_group = collect_filaments_in_groups<unsigned int>(groups[idx], iter->second);
float tmp_cost = 0;
std::optional<unsigned int>prev = current_extruder_id;
for (auto& f : sequence_in_group) {
if (prev) { tmp_cost += flush_matrix[idx][*prev][f]; }
prev = f;
}
cost += tmp_cost;
if (!sequence_in_group.empty())
current_extruder_id = sequence_in_group.back();
//insert an empty array
if (filament_sequences)
layer_sequences[idx].emplace_back(std::vector<unsigned int>());
continue;
}
std::vector<unsigned int>filament_used_in_group = collect_filaments_in_groups<unsigned int>(groups[idx], curr_lf);
std::vector<unsigned int>next_lf;
if (layer + 1 < layer_filaments.size())
next_lf = layer_filaments[layer + 1];
std::vector<unsigned int>filament_used_in_group_next_layer = collect_filaments_in_groups<unsigned int>(groups[idx], next_lf);
bool use_forcast = (filament_used_in_group.size() <= max_n_with_forcast && filament_used_in_group_next_layer.size() <= max_n_with_forcast);
float tmp_cost = 0;
std::vector<unsigned int>sequence;
uint128_t hash_key = extruders_to_hash_key(filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast);
if (auto iter = caches.find(hash_key); iter != caches.end()) {
tmp_cost = iter->second.first;
sequence = iter->second.second;
}
else {
sequence = get_extruders_order(flush_matrix[idx], filament_used_in_group, filament_used_in_group_next_layer, current_extruder_id, use_forcast, &tmp_cost);
caches[hash_key] = { tmp_cost,sequence };
}
assert(sequence.size() == filament_used_in_group.size());
if (filament_sequences)
layer_sequences[idx].emplace_back(sequence);
if (!sequence.empty())
current_extruder_id = sequence.back();
cost += tmp_cost;
}
}
// get the final layer sequences
// if only have one group,we need to check whether layer sequence[idx] is valid
if (filament_sequences) {
filament_sequences->clear();
filament_sequences->resize(layer_filaments.size());
int last_group_id = 0;
//if last_group == 0,print group 0 first ,else print group 1 first
if (!custom_layer_sequence_map.empty()) {
const auto& first_layer = custom_layer_sequence_map.begin()->first;
const auto& first_layer_filaments = custom_layer_sequence_map.begin()->second;
assert(!first_layer_filaments.empty());
bool first_group = groups[0].count(first_layer_filaments.front()) ? 0 : 1;
last_group_id = (first_layer & 1) ? !first_group : first_group;
}
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
auto& curr_layer_seq = (*filament_sequences)[layer];
if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) {
curr_layer_seq = custom_layer_sequence_map[layer];
if (!curr_layer_seq.empty()) {
last_group_id = groups[0].count(curr_layer_seq.back()) ? 0 : 1;
}
continue;
}
if (last_group_id == 1) {
// try reuse the last group
if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty())
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end());
if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) {
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end());
last_group_id = 0; // update last group id
}
}
else if(last_group_id == 0) {
if (!layer_sequences[0].empty() && !layer_sequences[0][layer].empty()) {
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[0][layer].begin(), layer_sequences[0][layer].end());
}
if (!layer_sequences[1].empty() && !layer_sequences[1][layer].empty()) {
curr_layer_seq.insert(curr_layer_seq.end(), layer_sequences[1][layer].begin(), layer_sequences[1][layer].end());
last_group_id = 1; // update last group id
}
}
}
}
return cost;
}
}

View File

@@ -0,0 +1,114 @@
#ifndef TOOL_ORDER_UTILS_HPP
#define TOOL_ORDER_UTILS_HPP
#include <vector>
#include <optional>
#include <functional>
#include <limits>
#include <memory>
#include <unordered_set>
namespace Slic3r {
using FlushMatrix = std::vector<std::vector<float>>;
namespace MaxFlowGraph {
const int INF = std::numeric_limits<int>::max();
const int INVALID_ID = -1;
}
class MaxFlowSolver
{
private:
struct Edge {
int from, to, capacity, flow;
Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {}
};
public:
MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
const std::vector<int>& u_capacity = {},
const std::vector<int>& v_capacity = {}
);
std::vector<int> solve();
private:
void add_edge(int from, int to, int capacity);
int total_nodes;
int source_id;
int sink_id;
std::vector<Edge>edges;
std::vector<int>l_nodes;
std::vector<int>r_nodes;
std::vector<std::vector<int>>adj;
};
struct MinCostMaxFlow;
class GeneralMinCostSolver
{
public:
GeneralMinCostSolver(const std::vector<std::vector<float>>& matrix_,
const std::vector<int>& u_nodes,
const std::vector<int>& v_nodes);
std::vector<int> solve();
~GeneralMinCostSolver();
private:
std::unique_ptr<MinCostMaxFlow> m_solver;
};
class MinFlushFlowSolver
{
public:
MinFlushFlowSolver(const std::vector<std::vector<float>>& matrix_,
const std::vector<int>& u_nodes,
const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
const std::vector<int>& u_capacity = {},
const std::vector<int>& v_capacity = {}
);
std::vector<int> solve();
~MinFlushFlowSolver();
private:
std::unique_ptr<MinCostMaxFlow> m_solver;
};
class MatchModeGroupSolver
{
public:
MatchModeGroupSolver(const std::vector<std::vector<float>>& matrix_,
const std::vector<int>& u_nodes,
const std::vector<int>& v_nodes,
const std::vector<int>& v_capacity,
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {});
std::vector<int> solve();
~MatchModeGroupSolver();
private:
std::unique_ptr<MinCostMaxFlow> m_solver;
};
std::vector<unsigned int> get_extruders_order(const std::vector<std::vector<float>> &wipe_volumes,
const std::vector<unsigned int> &curr_layer_extruders,
const std::vector<unsigned int> &next_layer_extruders,
const std::optional<unsigned int> &start_extruder_id,
bool use_forcast = false,
float *cost = nullptr);
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int> &filament_lists,
const std::vector<int> &filament_maps,
const std::vector<std::vector<unsigned int>> &layer_filaments,
const std::vector<FlushMatrix> &flush_matrix,
std::optional<std::function<bool(int, std::vector<int> &)>> get_custom_seq,
std::vector<std::vector<unsigned int>> *filament_sequences);
}
#endif // !TOOL_ORDER_UTILS_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,9 @@
#include <utility>
#include <boost/container/small_vector.hpp>
#include "../FilamentGroup.hpp"
#include "../ExtrusionEntity.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
@@ -91,6 +94,37 @@ private:
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
};
struct FilamentChangeStats
{
int filament_flush_weight{0};
int filament_change_count{0};
int extruder_change_count{0};
void clear(){
filament_flush_weight = 0;
filament_change_count = 0;
extruder_change_count = 0;
}
FilamentChangeStats& operator+=(const FilamentChangeStats& other) {
this->filament_flush_weight += other.filament_flush_weight;
this->filament_change_count += other.filament_change_count;
this->extruder_change_count += other.extruder_change_count;
return *this;
}
FilamentChangeStats operator+(const FilamentChangeStats& other){
FilamentChangeStats ret;
ret.filament_flush_weight = this->filament_flush_weight + other.filament_flush_weight;
ret.filament_change_count = this->filament_change_count + other.filament_change_count;
ret.extruder_change_count = this->extruder_change_count + other.extruder_change_count;
return ret;
}
};
class LayerTools
{
public:
@@ -146,6 +180,11 @@ private:
class ToolOrdering
{
public:
enum FilamentChangeMode {
SingleExt,
MultiExtBest,
MultiExtCurr
};
ToolOrdering() = default;
// For the use case when each object is printed separately
@@ -156,8 +195,17 @@ public:
// (print->config().print_sequence == PrintSequence::ByObject is false).
ToolOrdering(const Print& print, unsigned int first_extruder, bool prime_multi_material = false);
void clear() {
m_layer_tools.clear(); m_tool_order_cache.clear();
void handle_dontcare_extruder(const std::vector<unsigned int>& first_layer_tool_order);
void handle_dontcare_extruder(unsigned int first_extruder);
void sort_and_build_data(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
void sort_and_build_data(const Print& print, unsigned int first_extruder, bool prime_multi_material = false);
void clear() {
m_layer_tools.clear();
m_stats_by_single_extruder.clear();
m_stats_by_multi_extruder_best.clear();
m_stats_by_multi_extruder_curr.clear();
}
// Only valid for non-sequential print:
@@ -187,16 +235,31 @@ public:
std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; }
int get_most_used_extruder() const { return most_used_extruder; }
/*
* called in single extruder mode, the value in map are all 0
* called in dual extruder mode, the value in map will be 0 or 1
* 0 based group id
*/
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
// should be called after doing reorder
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);
void cal_most_used_extruder(const PrintConfig &config);
bool cal_non_support_filaments(const PrintConfig &config,
unsigned int & first_non_support_filament,
std::vector<int> & initial_non_support_filaments,
std::vector<int> & initial_filaments);
bool has_non_support_filament(const PrintConfig &config);
private:
void initialize_layers(std::vector<coordf_t> &zs);
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
void reorder_extruders(unsigned int last_extruder_id);
// BBS
void reorder_extruders(std::vector<unsigned int> tool_order_layer0);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height);
void collect_extruder_statistics(bool prime_multi_material);
void reorder_extruders_for_minimum_flush_volume();
void reorder_extruders_for_minimum_flush_volume(bool reorder_first_layer);
// BBS
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
@@ -209,11 +272,18 @@ private:
unsigned int m_last_printing_extruder = (unsigned int)-1;
// All extruders, which extrude some material over m_layer_tools.
std::vector<unsigned int> m_all_printing_extruders;
std::unordered_map<uint32_t, std::vector<uint8_t>> m_tool_order_cache;
const DynamicPrintConfig* m_print_full_config = nullptr;
const PrintConfig* m_print_config_ptr = nullptr;
const PrintObject* m_print_object_ptr = nullptr;
Print* m_print;
bool m_sorted = false;
bool m_is_BBL_printer = false;
FilamentChangeStats m_stats_by_single_extruder;
FilamentChangeStats m_stats_by_multi_extruder_curr;
FilamentChangeStats m_stats_by_multi_extruder_best;
int most_used_extruder;
};
} // namespace SLic3r

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,10 @@
#include <algorithm>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include <unordered_set>
namespace Slic3r
{
@@ -17,15 +21,21 @@ class PrintConfig;
enum GCodeFlavor : unsigned char;
class WipeTower
{
public:
friend class WipeTowerWriter;
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
// WipeTower height to minimum depth map
static const std::map<float, float> min_depth_per_height;
static float get_limit_depth_by_height(float max_height);
static float get_auto_brim_by_height(float max_height);
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
struct Extrusion
{
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
@@ -38,6 +48,18 @@ public:
unsigned int tool;
};
struct NozzleChangeResult
{
std::string gcode;
Vec2f start_pos; // rotated
Vec2f end_pos;
Vec2f origin_start_pos; // not rotated
std::vector<Vec2f> wipe_path;
};
struct ToolChangeResult
{
// Print heigh of this tool change.
@@ -60,6 +82,9 @@ public:
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
bool priming;
bool is_tool_change{false};
Vec2f tool_change_start_pos;
// Pass a polyline so that normal G-code generator can do a wipe for us.
// The wipe cannot be done by the wipe tower because it has to pass back
// a loaded extruder, so it would have to either do a wipe with no retraction
@@ -81,6 +106,8 @@ public:
// executing the gcode finish_layer_tcr.
bool is_finish_first = false;
NozzleChangeResult nozzle_change_result;
// Sum the total length of the extrusion.
float total_extrusion_length_in_plane() {
float e_length = 0.f;
@@ -133,14 +160,22 @@ public:
bool priming,
size_t old_tool,
bool is_finish,
bool is_tool_change,
float purge_volume) const;
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
bool priming,
size_t filament_id,
bool is_finish,
float purge_volume) const;
// x -- x coordinates of wipe tower in mm ( left bottom corner )
// y -- y coordinates of wipe tower in mm ( left bottom corner )
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
// wipe_area -- space available for one toolchange in mm
// BBS: add partplate logic
WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, const float wipe_volume, size_t initial_tool, const float wipe_tower_height);
WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, size_t initial_tool, const float wipe_tower_height, const std::vector<unsigned int>& slice_used_filaments);
// Set the extruder properties.
@@ -153,12 +188,27 @@ public:
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<ToolChangeResult>> &result);
WipeTower::ToolChangeResult only_generate_out_wall();
WipeTower::ToolChangeResult only_generate_out_wall(bool is_new_mode = false);
Polygon generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer);
Polygon generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points);
Polygon generate_rib_polygon(const box_coordinates &wt_box);
float get_depth() const { return m_wipe_tower_depth; }
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
BoundingBoxf get_bbx() const {
if (m_outer_wall.empty()) return BoundingBoxf({Vec2d(0,0)});
BoundingBox box = get_extents(m_outer_wall.begin()->second);
BoundingBoxf res = BoundingBoxf(unscale(box.min), unscale(box.max));
return res;
}
std::map<float, Polylines> get_outer_wall() const
{
return m_outer_wall;
}
float get_height() const { return m_wipe_tower_height; }
float get_layer_height() const { return m_layer_height; }
float get_rib_length() const { return m_rib_length; }
float get_rib_width() const { return m_rib_width; }
void set_last_layer_extruder_fill(bool extruder_fill) {
if (!m_plan.empty()) {
@@ -194,7 +244,7 @@ public:
m_num_tool_changes = 0;
} else
++ m_num_layer_changes;
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
m_extrusion_flow = extrusion_flow(layer_height);
@@ -213,7 +263,7 @@ public:
// Returns gcode to prime the nozzles at the front edge of the print bed.
std::vector<ToolChangeResult> prime(
// print_z of the first layer.
float initial_layer_print_height,
float initial_layer_print_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
@@ -225,6 +275,8 @@ public:
// BBS
ToolChangeResult tool_change(size_t new_tool, bool extrude_perimeter = false, bool first_toolchange_to_nonsoluble = false);
NozzleChangeResult nozzle_change(int old_filament_id, int new_filament_id);
// Fill the unfilled space with a sparse infill.
// Call this method only if layer_finished() is false.
ToolChangeResult finish_layer(bool extruder_perimeter = true, bool extruder_fill = true);
@@ -235,6 +287,12 @@ public:
if (layer_height < 0) return m_extrusion_flow;
return layer_height * (m_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area();
}
float nozzle_change_extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
{
if (layer_height < 0)
return m_extrusion_flow;
return layer_height * (m_nozzle_change_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area();
}
bool get_floating_area(float& start_pos_y, float& end_pos_y) const;
bool need_thick_bridge_flow(float pos_y) const;
@@ -248,8 +306,14 @@ public:
std::vector<float> get_used_filament() const { return m_used_filament_length; }
int get_number_of_toolchanges() const { return m_num_tool_changes; }
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
bool has_tpu_filament() const { return m_has_tpu_filament; }
struct FilamentParameters {
std::string material = "PLA";
int category;
bool is_soluble = false;
// BBS
bool is_support = false;
@@ -269,8 +333,65 @@ public:
std::vector<float> ramming_speed;
float nozzle_diameter;
float filament_area;
float retract_length;
float retract_speed;
float wipe_dist;
};
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
std::vector<int> m_used_filament_ids;
std::vector<int> m_filament_categories;
struct WipeTowerBlock
{
int block_id{0};
int filament_adhesiveness_category{0};
std::vector<float> layer_depths;
std::vector<bool> solid_infill;
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
float depth{0};
float start_depth{0};
float cur_depth{0};
int last_filament_change_id{-1};
int last_nozzle_change_id{-1};
};
struct BlockDepthInfo
{
int category{-1};
float depth{0};
float nozzle_change_depth{0};
};
std::vector<std::vector<BlockDepthInfo>> m_all_layers_depth;
std::vector<WipeTowerBlock> m_wipe_tower_blocks;
int m_last_block_id;
WipeTowerBlock* m_cur_block{nullptr};
// help function
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
int get_filament_category(int filament_id);
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
void reset_block_status();
int get_wall_filament_for_all_layer();
// for generate new wipe tower
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
void plan_tower_new();
void generate_wipe_tower_blocks();
void update_all_layer_depth(float wipe_tower_depth);
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
Vec2f get_rib_offset() const { return m_rib_offset; }
private:
enum wipe_shape // A fill-in direction
{
@@ -284,6 +405,9 @@ private:
return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point
}
int m_slice_used_filaments = 0;
int m_wrapping_detection_layers = 0;
bool m_enable_wrapping_detection = false;
bool m_enable_timelapse_print = false;
bool m_semm = true; // Are we using a single extruder multimaterial printer?
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
@@ -304,7 +428,22 @@ private:
float m_travel_speed = 0.f;
float m_first_layer_speed = 0.f;
size_t m_first_layer_idx = size_t(-1);
size_t m_cur_layer_id;
std::vector<double> m_filaments_change_length;
size_t m_cur_layer_id;
NozzleChangeResult m_nozzle_change_result;
std::vector<int> m_filament_map;
bool m_has_tpu_filament{false};
bool m_is_multi_extruder{false};
bool m_use_gap_wall{false};
bool m_use_rib_wall{false};
float m_rib_length=0.f;
float m_rib_width=0.f;
float m_extra_rib_length=0.f;
bool m_used_fillet{false};
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
bool m_tower_framework{false};
// G-code generator parameters.
float m_cooling_tube_retraction = 0.f;
float m_cooling_tube_length = 0.f;
@@ -326,6 +465,7 @@ private:
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
// Extruder specific parameters.
@@ -342,15 +482,16 @@ private:
size_t m_current_tool = 0;
// Orca: support mmu wipe tower
std::vector<std::vector<float>> wipe_volumes;
const float m_wipe_volume;
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
bool m_current_layer_finished = false;
bool m_left_to_right = true;
float m_extra_spacing = 1.f;
float m_tpu_fixed_spacing = 2;
std::vector<Vec2f> m_wall_skip_points;
std::map<float,Polylines> m_outer_wall;
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
bool m_flat_ironing=false;
// Calculates length of extrusion line to extrude given volume
float volume_to_length(float volume, float line_width, float layer_height) const {
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
@@ -362,9 +503,13 @@ private:
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
void make_wipe_tower_square();
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length);
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
void save_on_last_wipe();
bool is_tpu_filament(int filament_id) const;
// BBS
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
@@ -379,6 +524,7 @@ private:
float first_wipe_line;
float wipe_volume;
float wipe_length;
float nozzle_change_depth{0};
// BBS
float purge_volume;
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
@@ -411,7 +557,7 @@ private:
void toolchange_Unload(
WipeTowerWriter &writer,
const box_coordinates &cleaning_box,
const box_coordinates &cleaning_box,
const std::string& current_material,
const int new_temperature);
@@ -419,15 +565,16 @@ private:
WipeTowerWriter &writer,
const size_t new_tool,
const std::string& new_material);
void toolchange_Load(
WipeTowerWriter &writer,
const box_coordinates &cleaning_box);
void toolchange_Wipe(
WipeTowerWriter &writer,
const box_coordinates &cleaning_box,
float wipe_volume);
void get_wall_skip_points(const WipeTowerInfo &layer);
};
@@ -435,4 +582,4 @@ private:
} // namespace Slic3r
#endif // WipeTowerPrusaMM_hpp_
#endif // WipeTowerPrusaMM_hpp_

View File

@@ -24,7 +24,7 @@
namespace Slic3r
{
float flat_iron_area = 4.f;
static constexpr float flat_iron_area = 4.f;
constexpr float flat_iron_speed = 10.f * 60.f;
static const double wipe_tower_wall_infill_overlap = 0.0;
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
@@ -69,7 +69,7 @@ static bool is_valid_gcode(const std::string& gcode)
return is_valid;
}
Polygon chamfer_polygon(Polygon& polygon, double chamfer_dis = 2., double angle_tol = 30. / 180. * PI)
static Polygon chamfer_polygon(Polygon& polygon, double chamfer_dis = 2., double angle_tol = 30. / 180. * PI)
{
if (polygon.points.size() < 3)
return polygon;
@@ -104,7 +104,7 @@ Polygon chamfer_polygon(Polygon& polygon, double chamfer_dis = 2., double angle_
return res;
}
Polygon rounding_polygon(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI)
static Polygon rounding_polygon(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI)
{
if (polygon.points.size() < 3)
return polygon;
@@ -174,7 +174,7 @@ Polygon rounding_polygon(Polygon& polygon, double rounding = 2., double angle_to
return res;
}
Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI)
static Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double angle_tol = 30. / 180. * PI)
{
if (polygon.points.size() < 3)
return polygon;
@@ -234,7 +234,7 @@ Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double angle_
return res;
}
std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
static std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
{
const Vec2f v2 = c - b;
double denom = cross2(v1, v2);
@@ -252,14 +252,14 @@ std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, con
}
return std::pair<bool, Vec2f>(false, Vec2f{0, 0});
}
Polygon scale_polygon(const std::vector<Vec2f>& points)
static Polygon scale_polygon(const std::vector<Vec2f>& points)
{
Polygon res;
for (const auto& p : points)
res.points.push_back(scaled(p));
return res;
}
std::vector<Vec2f> unscale_polygon(const Polygon& polygon)
static std::vector<Vec2f> unscale_polygon(const Polygon& polygon)
{
std::vector<Vec2f> res;
for (const auto& p : polygon.points)
@@ -267,7 +267,7 @@ std::vector<Vec2f> unscale_polygon(const Polygon& polygon)
return res;
}
Polygon generate_rectange(const Line& line, coord_t offset)
static Polygon generate_rectange(const Line& line, coord_t offset)
{
Point p1 = line.a;
Point p2 = line.b;
@@ -306,7 +306,7 @@ struct Segment
bool is_valid() const { return start.y() < end.y(); }
};
std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
static std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
{
std::vector<Segment> result;
result.push_back(segment);
@@ -349,7 +349,7 @@ struct PointWithFlag
int pair_idx; // gap_pair idx
bool is_forward;
};
IntersectionInfo move_point_along_polygon(
static IntersectionInfo move_point_along_polygon(
const std::vector<Vec2f>& points, const Vec2f& startPoint, int startIdx, float offset, bool forward, int pair_idx)
{
float remainingDistance = offset;
@@ -412,7 +412,7 @@ IntersectionInfo move_point_along_polygon(
return res;
};
void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
{
int next = (idx + 1) % pl.size();
Vec2f pos1 = pl[idx].pos;
@@ -428,7 +428,7 @@ void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_
}
}
Polylines remove_points_from_polygon(
static Polylines remove_points_from_polygon(
const Polygon& polygon, const std::vector<Vec2f>& skip_points, double range, bool is_left, Polygon& insert_skip_pg)
{
assert(polygon.size() > 2);
@@ -519,7 +519,7 @@ Polylines remove_points_from_polygon(
return result;
}
Polylines contrust_gap_for_skip_points(
static Polylines contrust_gap_for_skip_points(
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon)
{
if (skip_points.empty()) {
@@ -534,7 +534,7 @@ Polylines contrust_gap_for_skip_points(
return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon);
};
Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
{
Polygon res;
res.points.push_back(scaled(wt_box_min));
@@ -2090,7 +2090,7 @@ std::vector<std::vector<float>> WipeTower2::extract_wipe_volumes(const PrintConf
{
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
std::vector<float> wiping_matrix(cast<float>(config.flush_volumes_matrix.values));
auto scale = config.flush_multiplier;
auto scale = config.flush_multiplier.get_at(0);
// The values shall only be used when SEMM is enabled. The purging for other printers
// is determined by filament_minimal_purge_on_wipe_tower.