ENH: smoothing z direction speed
Jira: none get z direction continuous info smoothing speed based on the max speed of the loop rewite the pipeline set perset param to enable z smoothing function Signed-off-by: qing.zhang <qing.zhang@bambulab.com> Change-Id: Ib0e6a6a353c92a441a0c99a8d3d7902f7aeae6df
This commit is contained in:
parent
3f94e72cae
commit
e7b9e81e84
|
@ -178,5 +178,6 @@
|
|||
"0",
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"z_direction_outwall_speed_continuous": "1"
|
||||
}
|
|
@ -139,8 +139,8 @@ set(lisbslic3r_sources
|
|||
Format/svg.cpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/GCodeEditer.cpp
|
||||
GCode/GCodeEditer.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
# GCode/PressureEqualizer.cpp
|
||||
|
@ -163,6 +163,10 @@ set(lisbslic3r_sources
|
|||
GCode/AvoidCrossingPerimeters.hpp
|
||||
GCode/ConflictChecker.cpp
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode/Smoothing.cpp
|
||||
GCode/Smoothing.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode.cpp
|
||||
GCode.hpp
|
||||
GCodeReader.cpp
|
||||
|
|
|
@ -16,6 +16,30 @@ using ExPolygons = std::vector<ExPolygon>;
|
|||
class ExtrusionEntityCollection;
|
||||
class Extruder;
|
||||
|
||||
|
||||
struct NodeContour
|
||||
{
|
||||
Points pts; //for lines contour
|
||||
std::vector<coord_t> widths;
|
||||
bool is_loop;
|
||||
};
|
||||
|
||||
struct LoopNode
|
||||
{
|
||||
//store outer wall and mark if it's loop
|
||||
NodeContour node_contour;
|
||||
int node_id;
|
||||
int loop_id = 0;
|
||||
BoundingBox bbox;
|
||||
int merged_id = -1;
|
||||
|
||||
//upper loop info
|
||||
std::vector<int> upper_node_id;
|
||||
|
||||
//lower loop info
|
||||
std::vector<int> lower_node_id;
|
||||
};
|
||||
|
||||
// Each ExtrusionRole value identifies a distinct set of { extruder, speed }
|
||||
enum ExtrusionRole : uint8_t {
|
||||
erNone,
|
||||
|
|
|
@ -31,9 +31,12 @@ public:
|
|||
|
||||
ExtrusionEntitiesPtr entities; // we own these entities
|
||||
bool no_sort;
|
||||
|
||||
std::pair<int, int> loop_node_range;
|
||||
ExtrusionEntityCollection(): no_sort(false) {}
|
||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse) { this->append(other.entities); }
|
||||
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse) {}
|
||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) { this->append(other.entities); }
|
||||
ExtrusionEntityCollection(ExtrusionEntityCollection &&other)
|
||||
: entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) {}
|
||||
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
|
||||
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
|
||||
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
|
||||
|
@ -41,6 +44,7 @@ public:
|
|||
this->entities = std::move(other.entities);
|
||||
this->no_sort = other.no_sort;
|
||||
is_reverse = other.is_reverse;
|
||||
loop_node_range = other.loop_node_range;
|
||||
return *this;
|
||||
}
|
||||
~ExtrusionEntityCollection() { clear(); }
|
||||
|
|
|
@ -67,6 +67,8 @@ using namespace std::literals::string_view_literals;
|
|||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <libslic3r/GCode/Smoothing.hpp>
|
||||
#include <libslic3r/GCode/CoolingBuffer.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -1954,8 +1956,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||
|
||||
print.throw_if_canceled();
|
||||
|
||||
m_cooling_buffer = make_unique<CoolingBuffer>(*this);
|
||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||
m_gcode_editer = make_unique<GCodeEditer>(*this);
|
||||
m_gcode_editer->set_current_extruder(initial_extruder_id);
|
||||
|
||||
int extruder_id = get_extruder_id(initial_extruder_id);
|
||||
|
||||
|
@ -2257,7 +2259,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||
if (finished_objects > 0) {
|
||||
// Move to the origin position for the copy we're going to print.
|
||||
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through GCodeEditer
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
// BBS. change tool before moving to origin point.
|
||||
if (m_writer.need_toolchange(initial_extruder_id)) {
|
||||
|
@ -2285,8 +2287,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||
file.writeln(printing_by_object_gcode);
|
||||
}
|
||||
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
|
||||
m_cooling_buffer->reset(this->writer().get_position());
|
||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||
m_gcode_editer->reset(this->writer().get_position());
|
||||
m_gcode_editer->set_current_extruder(initial_extruder_id);
|
||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
||||
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
||||
// and export G-code into file.
|
||||
|
@ -2564,8 +2566,17 @@ void GCode::process_layers(
|
|||
const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> &layers_to_print,
|
||||
GCodeOutputStream &output_stream)
|
||||
{
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
//BBS: get object label id
|
||||
size_t layer_to_print_idx = 0;
|
||||
std::vector<int> object_label;
|
||||
|
||||
for (const PrintInstance *instance : print_object_instances_ordering)
|
||||
object_label.push_back(instance->model_instance->get_labeled_id());
|
||||
|
||||
std::vector<GCode::LayerResult> layers_results;
|
||||
layers_results.resize(layers_to_print.size());
|
||||
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
const auto generator = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if (layer_to_print_idx == layers_to_print.size()) {
|
||||
|
@ -2594,21 +2605,87 @@ void GCode::process_layers(
|
|||
slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult {
|
||||
spiral_mode.enable(in.spiral_vase_enable);
|
||||
bool last_layer = in.layer_id == layers_to_print.size() - 1;
|
||||
return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
|
||||
return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos};
|
||||
});
|
||||
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
});
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
);
|
||||
|
||||
std::vector<std::vector<PerExtruderAdjustments>> layers_extruder_adjustments(layers_to_print.size());
|
||||
|
||||
const auto parsing = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{
|
||||
//record gcode
|
||||
in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false);
|
||||
return std::move(in);
|
||||
});
|
||||
|
||||
//step2: cooling
|
||||
std::vector<std::vector<OutwallCollection>> layers_wall_collection(layers_to_print.size());
|
||||
|
||||
CoolingBuffer cooling_processor;
|
||||
|
||||
const auto cooling = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult {
|
||||
in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
return std::move(in);
|
||||
});
|
||||
|
||||
// step 4.1: record node date
|
||||
SmoothCalculator smooth_calculator(object_label.size());
|
||||
|
||||
const auto build_node = tbb::make_filter<GCode::LayerResult, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){
|
||||
smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
layers_results[in.gcode_store_pos] = std::move(in);
|
||||
return;
|
||||
});
|
||||
|
||||
// step 5: rewite
|
||||
const auto write_gocde= tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string {
|
||||
return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
});
|
||||
|
||||
std::vector<GCode::LayerResult> gcode_res;
|
||||
|
||||
// BBS: apply new feedrate of outwall and recalculate layer time
|
||||
int layer_idx = 0;
|
||||
const auto calculate_layer_time= tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &smooth_calculator, &layers_extruder_adjustments, &gcode_res](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if(layer_idx == gcode_res.size()){
|
||||
fc.stop();
|
||||
return{};
|
||||
}else{
|
||||
if (layer_idx > 0){
|
||||
gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]);
|
||||
}
|
||||
return gcode_res[layer_idx++];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); });
|
||||
|
||||
// BBS: apply cooling
|
||||
// The pipeline elements are joined using const references, thus no copying is performed.
|
||||
if (m_spiral_vase)
|
||||
tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output);
|
||||
else
|
||||
tbb::parallel_pipeline(12, generator & cooling & output);
|
||||
tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output);
|
||||
else if (!m_config.z_direction_outwall_speed_continuous)
|
||||
tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output);
|
||||
else {
|
||||
tbb::parallel_pipeline(12, generator & parsing & cooling & build_node);
|
||||
|
||||
//append data
|
||||
for (const LayerResult &res : layers_results) {
|
||||
//remove empty gcode layer caused by support independent layers
|
||||
if (res.cooling_buffer_flush) {
|
||||
smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]);
|
||||
gcode_res.push_back(std::move(res));
|
||||
}
|
||||
}
|
||||
|
||||
smooth_calculator.smooth_layer_speed();
|
||||
|
||||
tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
||||
|
@ -2623,8 +2700,21 @@ void GCode::process_layers(
|
|||
// BBS
|
||||
const bool prime_extruder)
|
||||
{
|
||||
// the pipeline should be
|
||||
// generatotr + (spira) + parse + (cooling) + (smoothing) + rewrite
|
||||
// rewrite pipeline to get better schu
|
||||
|
||||
// BBS: get object label id
|
||||
size_t layer_to_print_idx = 0;
|
||||
std::vector<int> object_label;
|
||||
for (LayerToPrint layer : layers_to_print)
|
||||
object_label.push_back(layer.original_object->instances()[single_object_idx].model_instance->get_labeled_id());
|
||||
|
||||
std::vector<GCode::LayerResult> layers_results;
|
||||
layers_results.resize(layers_to_print.size());
|
||||
|
||||
//step 1: generator
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
size_t layer_to_print_idx = 0;
|
||||
const auto generator = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if (layer_to_print_idx == layers_to_print.size()) {
|
||||
|
@ -2636,7 +2726,9 @@ void GCode::process_layers(
|
|||
//BBS
|
||||
check_placeholder_parser_failed();
|
||||
print.throw_if_canceled();
|
||||
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder);
|
||||
GCode::LayerResult res = this->process_layer(print, {std::move(layer)}, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder);
|
||||
res.gcode_store_pos = layer_to_print_idx - 1;
|
||||
return std::move(res);
|
||||
}
|
||||
});
|
||||
if (m_spiral_vase) {
|
||||
|
@ -2648,21 +2740,92 @@ void GCode::process_layers(
|
|||
slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult {
|
||||
spiral_mode.enable(in.spiral_vase_enable);
|
||||
bool last_layer = in.layer_id == layers_to_print.size() - 1;
|
||||
return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
|
||||
return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos};
|
||||
});
|
||||
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
});
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
);
|
||||
|
||||
//BBS: get objects and nodes info, for better arrange
|
||||
const ConstPrintObjectPtrsAdaptor &objects = print.objects();
|
||||
|
||||
// step 2: parse
|
||||
std::vector<std::vector<PerExtruderAdjustments>> layers_extruder_adjustments(layers_to_print.size());
|
||||
|
||||
const auto parsing = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{
|
||||
//record gcode
|
||||
in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false);
|
||||
return std::move(in);
|
||||
});
|
||||
|
||||
// step 3: cooling
|
||||
std::vector<std::vector<OutwallCollection>> layers_wall_collection(layers_to_print.size());
|
||||
CoolingBuffer cooling_processor;
|
||||
|
||||
const auto cooling = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult {
|
||||
in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
return std::move(in);
|
||||
});
|
||||
|
||||
// step 4.1: record node date
|
||||
SmoothCalculator smooth_calculator(object_label.size());
|
||||
|
||||
const auto build_node = tbb::make_filter<GCode::LayerResult, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){
|
||||
smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
layers_results[in.gcode_store_pos] = std::move(in);
|
||||
return;
|
||||
});
|
||||
|
||||
// step 5: rewite
|
||||
const auto write_gocde= tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string {
|
||||
return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]);
|
||||
});
|
||||
|
||||
std::vector<GCode::LayerResult> gcode_res;
|
||||
|
||||
// BBS: apply new feedrate of outwall and recalculate layer time
|
||||
int layer_idx = 0;
|
||||
//restart pipeline
|
||||
const auto calculate_layer_time = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &gcode_res, &smooth_calculator, &layers_extruder_adjustments](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if(layer_idx == gcode_res.size()){
|
||||
fc.stop();
|
||||
return{};
|
||||
}else{
|
||||
if (layer_idx > 0) {
|
||||
gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]);
|
||||
}
|
||||
return gcode_res[layer_idx++];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); });
|
||||
|
||||
// BBS: apply cooling
|
||||
// The pipeline elements are joined using const references, thus no copying is performed.
|
||||
if (m_spiral_vase)
|
||||
tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output);
|
||||
else
|
||||
tbb::parallel_pipeline(12, generator & cooling & output);
|
||||
tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output);
|
||||
else if (!m_config.z_direction_outwall_speed_continuous)
|
||||
tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output);
|
||||
else {
|
||||
tbb::parallel_pipeline(12, generator & parsing & cooling & build_node);
|
||||
// step 4.2: smoothing
|
||||
// break pipeline and do z smoothing
|
||||
// append data
|
||||
for (const LayerResult &res : layers_results) {
|
||||
// remove empty gcode layer caused by support independent layers
|
||||
if (res.cooling_buffer_flush) {
|
||||
smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]);
|
||||
gcode_res.push_back(res);
|
||||
}
|
||||
}
|
||||
|
||||
smooth_calculator.smooth_layer_speed();
|
||||
|
||||
tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override)
|
||||
|
@ -3526,7 +3689,8 @@ GCode::LayerResult GCode::process_layer(
|
|||
// The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice:
|
||||
std::vector<unsigned int> printing_extruders;
|
||||
for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) {
|
||||
for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) {
|
||||
bool is_infill = entity_type == ObjectByExtruder::Island::Region::INFILL;
|
||||
for (const ExtrusionEntity *ee : is_infill ? layerm->fills.entities : layerm->perimeters.entities) {
|
||||
// extrusions represents infill or perimeter extrusions of a single island.
|
||||
assert(dynamic_cast<const ExtrusionEntityCollection*>(ee) != nullptr);
|
||||
const auto *extrusions = static_cast<const ExtrusionEntityCollection*>(ee);
|
||||
|
@ -3579,6 +3743,15 @@ GCode::LayerResult GCode::process_layer(
|
|||
if (islands[island_idx].by_region.empty())
|
||||
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
|
||||
islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides);
|
||||
int start = extrusions->loop_node_range.first;
|
||||
int end = extrusions->loop_node_range.second;
|
||||
//BBS: add merged node infor
|
||||
if (!is_infill) {
|
||||
for (; start < end; ++start) {
|
||||
const LoopNode *node = &layer.loop_nodes[start];
|
||||
islands[island_idx].by_region[region.print_region_id()].merged_node.emplace_back(node);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -3713,6 +3886,8 @@ GCode::LayerResult GCode::process_layer(
|
|||
if (m_config.reduce_crossing_wall)
|
||||
m_avoid_crossing_perimeters.init_layer(*m_layer);
|
||||
|
||||
//BBS: label object id, prepare for cooling
|
||||
gcode += "; OBJECT_ID: " + std::to_string(instance_to_print.label_object_id) + "\n";
|
||||
std::string temp_start_str;
|
||||
if (m_enable_label_object) {
|
||||
std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n";
|
||||
|
@ -3918,8 +4093,8 @@ GCode::LayerResult GCode::process_layer(
|
|||
gcode = m_spiral_vase->process_layer(std::move(gcode));
|
||||
|
||||
// Apply cooling logic; this may alter speeds.
|
||||
if (m_cooling_buffer)
|
||||
gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(),
|
||||
if (m_gcode_editer)
|
||||
gcode = m_gcode_editer->process_layer(std::move(gcode), layer.id(),
|
||||
// Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen).
|
||||
object_layer || last_layer);
|
||||
|
||||
|
@ -4435,8 +4610,18 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
|
|||
if (! region.perimeters.empty()) {
|
||||
m_config.apply(print.get_print_region(®ion - &by_region.front()).config());
|
||||
|
||||
for (const ExtrusionEntity* ee : region.perimeters)
|
||||
// BBS: output merged node id
|
||||
int curr_node=0;
|
||||
|
||||
for (size_t perimeter_idx = 0; perimeter_idx < region.perimeters.size(); ++perimeter_idx) {
|
||||
const ExtrusionEntity *ee = region.perimeters[perimeter_idx];
|
||||
if (curr_node < region.merged_node.size() && perimeter_idx == region.merged_node[curr_node]->loop_id) {
|
||||
gcode += "; COOLING_NODE: " + std::to_string(region.merged_node[curr_node]->merged_id) + "\n";
|
||||
curr_node++;
|
||||
}
|
||||
|
||||
gcode += this->extrude_entity(*ee, "perimeter", -1.);
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "PlaceholderParser.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCode/AvoidCrossingPerimeters.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
#include "GCode/GCodeEditer.hpp"
|
||||
#include "GCode/RetractWhenCrossingPerimeters.hpp"
|
||||
#include "GCode/SpiralVase.hpp"
|
||||
#include "GCode/ToolOrdering.hpp"
|
||||
|
@ -303,7 +303,9 @@ private:
|
|||
// Should the cooling buffer content be flushed at the end of this layer?
|
||||
bool cooling_buffer_flush { false };
|
||||
// the layer store pos of gcode
|
||||
size_t gcode_store_pos;
|
||||
size_t gcode_store_pos = 0;
|
||||
//store each layer_time
|
||||
float layer_time = 0;
|
||||
LayerResult() = default;
|
||||
LayerResult(const std::string& gcode_, const size_t layer_id_, const bool spiral_vase_enable_, const bool cooling_buffer_flush_, const size_t gcode_store_pos_ = static_cast<size_t>(-1)) :
|
||||
gcode(gcode_), layer_id(layer_id_), spiral_vase_enable(spiral_vase_enable_), cooling_buffer_flush(cooling_buffer_flush_), gcode_store_pos(gcode_store_pos_){}
|
||||
|
@ -315,6 +317,7 @@ private:
|
|||
spiral_vase_enable = other.spiral_vase_enable;
|
||||
cooling_buffer_flush = other.cooling_buffer_flush;
|
||||
gcode_store_pos = other.gcode_store_pos;
|
||||
layer_time = other.layer_time;
|
||||
}
|
||||
|
||||
LayerResult& operator=(LayerResult&& other) noexcept {
|
||||
|
@ -324,6 +327,7 @@ private:
|
|||
spiral_vase_enable = other.spiral_vase_enable;
|
||||
cooling_buffer_flush = other.cooling_buffer_flush;
|
||||
gcode_store_pos = other.gcode_store_pos;
|
||||
layer_time = other.layer_time;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -400,7 +404,7 @@ private:
|
|||
ExtrusionEntitiesPtr perimeters;
|
||||
// Non-owned references to LayerRegion::fills::entities
|
||||
ExtrusionEntitiesPtr infills;
|
||||
|
||||
std::vector<const LoopNode*> merged_node;
|
||||
std::vector<const WipingExtrusions::ExtruderPerCopy*> infills_overrides;
|
||||
std::vector<const WipingExtrusions::ExtruderPerCopy*> perimeters_overrides;
|
||||
|
||||
|
@ -514,7 +518,7 @@ private:
|
|||
Point m_last_pos;
|
||||
bool m_last_pos_defined;
|
||||
bool m_last_scarf_seam_flag;
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
std::unique_ptr<GCodeEditer> m_gcode_editer;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,70 +1,21 @@
|
|||
#ifndef slic3r_CoolingBuffer_hpp_
|
||||
#define slic3r_CoolingBuffer_hpp_
|
||||
|
||||
#ifndef slic3r_CoolingProcess_hpp_
|
||||
#define slic3r_CoolingProcess_hpp_
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cfloat>
|
||||
#include "GCodeEditer.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
// A standalone G-code filter, to control cooling of the print.
|
||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||
// and the print is modified to stretch over a minimum layer time.
|
||||
//
|
||||
// The simple it sounds, the actual implementation is significantly more complex.
|
||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||
// For example, some materials may not like to print too slowly, while with some materials
|
||||
// we may slow down significantly.
|
||||
//
|
||||
class CoolingBuffer {
|
||||
class CoolingBuffer
|
||||
{
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
CoolingBuffer(){};
|
||||
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// BBS: X,Y,Z,E,F,I,J
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
int m_additional_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
// Printing extruder IDs, zero based.
|
||||
std::vector<unsigned int> m_extruder_ids;
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
|
||||
// Old logic: proportional.
|
||||
bool m_cooling_logic_proportional = false;
|
||||
//BBS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
//BBS:
|
||||
bool m_set_fan_changing_layer = false;
|
||||
bool m_set_addition_fan_changing_layer = false;
|
||||
bool m_cooling_logic_proportional = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,573 @@
|
|||
#include "../GCode.hpp"
|
||||
#include "GCodeEditer.hpp"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iostream>
|
||||
#include <float.h>
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
GCodeEditer::GCodeEditer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
const std::vector<Extruder> &extruders = gcodegen.writer().extruders();
|
||||
m_extruder_ids.reserve(extruders.size());
|
||||
for (const Extruder &ex : extruders) {
|
||||
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
|
||||
m_extruder_ids.emplace_back(ex.id());
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeEditer::reset(const Vec3d &position)
|
||||
{
|
||||
// BBS: add I and J axis to store center of arc
|
||||
m_current_pos.assign(7, 0.f);
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.get_at(get_extruder_index(m_config, m_current_extruder)));
|
||||
m_fan_speed = -1;
|
||||
m_additional_fan_speed = -1;
|
||||
m_current_fan_speed = -1;
|
||||
}
|
||||
|
||||
static void record_wall_lines(bool &flag, int &line_idx, PerExtruderAdjustments *adjustment, const std::pair<int, int> &node_pos)
|
||||
{
|
||||
if (flag && line_idx < adjustment->lines.size()) {
|
||||
CoolingLine &ptr = adjustment->lines[line_idx];
|
||||
ptr.outwall_smooth_mark = true;
|
||||
ptr.object_id = node_pos.first;
|
||||
ptr.cooling_node_id = node_pos.second;
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_node_pos(
|
||||
bool &flag, int &line_idx, std::pair<int, int> &node_pos, const std::vector<int> &object_label, int cooling_node_id, int object_id, PerExtruderAdjustments *adjustment)
|
||||
{
|
||||
for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) {
|
||||
if (object_label[object_idx] == object_id) {
|
||||
if (cooling_node_id == -1) break;
|
||||
line_idx = adjustment->lines.size();
|
||||
flag = true;
|
||||
node_pos.first = object_idx;
|
||||
node_pos.second = cooling_node_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeEditer::process_layer(std::string && gcode,
|
||||
const size_t layer_id,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
const std::vector<int> & object_label,
|
||||
const bool flush,
|
||||
const bool spiral_vase)
|
||||
{
|
||||
// Cache the input G-code.
|
||||
if (m_gcode.empty())
|
||||
m_gcode = std::move(gcode);
|
||||
else
|
||||
m_gcode += gcode;
|
||||
|
||||
std::string out;
|
||||
if (flush) {
|
||||
// This is either an object layer or the very last print layer. Calculate cool down over the collected support layers
|
||||
// and one object layer.
|
||||
// record parse gcode info to per_extruder_adjustments
|
||||
per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos, object_label, spiral_vase, layer_id > 0);
|
||||
out = m_gcode;
|
||||
m_gcode.clear();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//native-resource://sandbox_fs/webcontent/resource/assets/img/41ecc25c56.png
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> GCodeEditer::parse_layer_gcode(
|
||||
const std::string &gcode,
|
||||
std::vector<float> & current_pos,
|
||||
const std::vector<int> & object_label,
|
||||
bool spiral_vase,
|
||||
bool join_z_smooth)
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
for (size_t i = 0; i < m_extruder_ids.size(); ++ i) {
|
||||
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
|
||||
unsigned int extruder_id = m_extruder_ids[i];
|
||||
adj.extruder_id = extruder_id;
|
||||
adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id);
|
||||
adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id));
|
||||
adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id));
|
||||
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
||||
}
|
||||
|
||||
unsigned int current_extruder = m_parse_gcode_extruder;
|
||||
PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
const char *line_start = gcode.c_str();
|
||||
const char *line_end = line_start;
|
||||
// Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
int object_id = -1;
|
||||
int cooling_node_id = -1;
|
||||
std::string object_id_string = "; OBJECT_ID: ";
|
||||
std::string cooling_node_label = "; COOLING_NODE: ";
|
||||
bool append_wall_ptr = false;
|
||||
bool append_inner_wall_ptr = false;
|
||||
|
||||
std::pair<int, int> node_pos;
|
||||
int line_idx = -1;
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
++ line_end;
|
||||
// sline will not contain the trailing '\n'.
|
||||
std::string sline(line_start, line_end);
|
||||
// CoolingLine will contain the trailing '\n'.
|
||||
if (*line_end == '\n')
|
||||
++ line_end;
|
||||
CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
|
||||
if (boost::starts_with(sline, "G0 "))
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
line.type = CoolingLine::TYPE_G2;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
line.type = CoolingLine::TYPE_G3;
|
||||
//BBS: parse object id & node id
|
||||
else if (boost::starts_with(sline, object_id_string)) {
|
||||
std::string sub = sline.substr(object_id_string.size());
|
||||
object_id = std::stoi(sub);
|
||||
} else if (boost::starts_with(sline, cooling_node_label)) {
|
||||
std::string sub = sline.substr(cooling_node_label.size());
|
||||
cooling_node_id = std::stoi(sub);
|
||||
}
|
||||
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// Parse the G-code line.
|
||||
std::vector<float> new_pos(current_pos);
|
||||
const char *c = sline.data() + 3;
|
||||
for (;;) {
|
||||
// Skip whitespaces.
|
||||
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||
if (*c == 0 || *c == ';')
|
||||
break;
|
||||
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
//BBS: Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == 'E') ? 3 : (*c == 'F') ? 4 :
|
||||
(*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
new_pos[axis] = float(atof(++c));
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= CoolingLine::TYPE_HAS_F;
|
||||
} else if (axis == 5 || axis == 6) {
|
||||
// BBS: get position of arc center
|
||||
new_pos[axis] += current_pos[axis - 5];
|
||||
}
|
||||
}
|
||||
// Skip this word.
|
||||
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
|
||||
}
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
|
||||
record_wall_lines(append_inner_wall_ptr, line_idx, adjustment, node_pos);
|
||||
|
||||
if (wipe)
|
||||
line.type |= CoolingLine::TYPE_WIPE;
|
||||
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
|
||||
line.type |= CoolingLine::TYPE_ADJUSTABLE;
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
|
||||
record_wall_lines(append_wall_ptr, line_idx, adjustment, node_pos);
|
||||
|
||||
if (external_perimeter) {
|
||||
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
|
||||
if (line.type & CoolingLine::TYPE_ADJUSTABLE && join_z_smooth && !spiral_vase) {
|
||||
// BBS: collect outwall info
|
||||
mark_node_pos(append_wall_ptr, line_idx, node_pos, object_label, cooling_node_id, object_id, adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
//BBS: G0, G1, G2, G3. Calculate the duration.
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = 0;
|
||||
//BBS: support to calculate length of arc
|
||||
if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) {
|
||||
Vec3f start(current_pos[0], current_pos[1], 0);
|
||||
Vec3f end(new_pos[0], new_pos[1], 0);
|
||||
Vec3f center(new_pos[5], new_pos[6], 0);
|
||||
bool is_ccw = line.type & CoolingLine::TYPE_G3;
|
||||
float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw);
|
||||
dxy2 = dxy * dxy;
|
||||
} else {
|
||||
dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
}
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
if (dxyz2 > 0.f) {
|
||||
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||
line.length = sqrt(dxyz2);
|
||||
} else if (std::abs(dif[3]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
line.origin_feedrate = new_pos[4];
|
||||
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0)
|
||||
line.time = line.length / line.feedrate;
|
||||
|
||||
if (line.feedrate == 0)
|
||||
line.time = 0;
|
||||
|
||||
line.time_max = line.time;
|
||||
if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
|
||||
line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed);
|
||||
line.origin_time_max = line.time_max;
|
||||
// BBS: add G2 and G3 support
|
||||
if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) ||
|
||||
(line.type & CoolingLine::TYPE_G2) ||
|
||||
(line.type & CoolingLine::TYPE_G3))) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
|
||||
CoolingLine &sm = adjustment->lines[active_speed_modifier];
|
||||
assert(sm.feedrate > 0.f);
|
||||
sm.length += line.length;
|
||||
sm.time += line.time;
|
||||
if (sm.time_max != FLT_MAX) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
sm.time_max = FLT_MAX;
|
||||
else
|
||||
sm.time_max += line.time_max;
|
||||
|
||||
sm.origin_time_max = sm.time_max;
|
||||
}
|
||||
// Don't store this line.
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
active_speed_modifier = size_t(-1);
|
||||
} else if (boost::starts_with(sline, m_toolchange_prefix)) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size());
|
||||
// Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored.
|
||||
if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) {
|
||||
if (new_extruder != current_extruder) {
|
||||
// Switch the tool.
|
||||
line.type = CoolingLine::TYPE_SET_TOOL;
|
||||
current_extruder = new_extruder;
|
||||
adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway.
|
||||
if (map_extruder_to_per_extruder_adjustment.size() > 1)
|
||||
BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline;
|
||||
}
|
||||
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_START;
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_END;
|
||||
} else if (boost::starts_with(sline, "G4 ")) {
|
||||
// Parse the wait time.
|
||||
line.type = CoolingLine::TYPE_G4;
|
||||
size_t pos_S = sline.find('S', 3);
|
||||
size_t pos_P = sline.find('P', 3);
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
line.time = line.time_max = float(
|
||||
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
|
||||
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||
line.origin_time_max = line.time_max;
|
||||
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
|
||||
} else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) {
|
||||
line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER;
|
||||
}
|
||||
if (line.type != 0)
|
||||
adjustment->lines.emplace_back(std::move(line));
|
||||
}
|
||||
m_parse_gcode_extruder = current_extruder;
|
||||
return per_extruder_adjustments;
|
||||
}
|
||||
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string GCodeEditer::write_layer_gcode(
|
||||
// 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,
|
||||
// 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.
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
if (gcode.empty())
|
||||
return gcode;
|
||||
|
||||
// First sort the adjustment lines by of multiple extruders by their position in the source G-code.
|
||||
std::vector<const CoolingLine*> lines;
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
n_lines += adj.lines.size();
|
||||
lines.reserve(n_lines);
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
for (const CoolingLine &line : adj.lines)
|
||||
lines.emplace_back(&line);
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
|
||||
}
|
||||
// Second generate the adjusted G-code.
|
||||
std::string new_gcode;
|
||||
new_gcode.reserve(gcode.size() * 2);
|
||||
bool overhang_fan_control= false;
|
||||
int overhang_fan_speed = 0;
|
||||
|
||||
enum class SetFanType {
|
||||
sfChangingLayer = 0,
|
||||
sfChangingFilament,
|
||||
sfImmediatelyApply
|
||||
};
|
||||
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed](SetFanType type) {
|
||||
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
|
||||
int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed);
|
||||
int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0;
|
||||
//BBS
|
||||
int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers);
|
||||
// Is the fan speed ramp enabled?
|
||||
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
|
||||
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) {
|
||||
// When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero,
|
||||
// so there will be a zero fan speed at least at the 1st layer.
|
||||
close_fan_the_first_x_layers = 1;
|
||||
}
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers) {
|
||||
int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
|
||||
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
|
||||
float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time));
|
||||
//BBS: always enable the fan speed interpolation according to layer time
|
||||
//if (EXTRUDER_CONFIG(cooling)) {
|
||||
if (layer_time < slow_down_layer_time) {
|
||||
// Layer time very short. Enable the fan to a full throttle.
|
||||
fan_speed_new = fan_max_speed;
|
||||
} else if (layer_time < fan_cooling_layer_time) {
|
||||
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
||||
assert(layer_time >= slow_down_layer_time);
|
||||
double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time);
|
||||
fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5);
|
||||
}
|
||||
//}
|
||||
overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed);
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) {
|
||||
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
|
||||
}
|
||||
#undef EXTRUDER_CONFIG
|
||||
overhang_fan_control= overhang_fan_speed > fan_speed_new;
|
||||
} else {
|
||||
overhang_fan_control= false;
|
||||
overhang_fan_speed = 0;
|
||||
fan_speed_new = 0;
|
||||
additional_fan_speed_new = 0;
|
||||
}
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
//BBS
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_fan_changing_layer = true;
|
||||
//BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
//BBS
|
||||
if (additional_fan_speed_new != m_additional_fan_speed) {
|
||||
m_additional_fan_speed = additional_fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_addition_fan_changing_layer = true;
|
||||
//BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
};
|
||||
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
//BBS
|
||||
m_set_fan_changing_layer = false;
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
change_extruder_set_fan(SetFanType::sfChangingLayer);
|
||||
for (const CoolingLine *line : lines) {
|
||||
const char *line_start = gcode.c_str() + line->line_start;
|
||||
const char *line_end = gcode.c_str() + line->line_end;
|
||||
if (line_start > pos)
|
||||
new_gcode.append(pos, line_start - pos);
|
||||
if (line->type & CoolingLine::TYPE_SET_TOOL) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size());
|
||||
if (new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
change_extruder_set_fan(SetFanType::sfChangingFilament); //BBS: will force to resume fan speed when filament change is finished
|
||||
}
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) {
|
||||
if (overhang_fan_control) {
|
||||
//BBS
|
||||
m_current_fan_speed = overhang_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) {
|
||||
if (overhang_fan_control) {
|
||||
//BBS
|
||||
m_current_fan_speed = m_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) {
|
||||
//BBS: force to write a fan speed command again
|
||||
if (m_current_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
if (m_additional_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) {
|
||||
//BBS: check whether fan speed need to changed when change layer
|
||||
if (m_current_fan_speed != -1 && m_set_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
m_set_fan_changing_layer = false;
|
||||
}
|
||||
if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
}
|
||||
}
|
||||
else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
|
||||
// Find the start of a comment, or roll to the end of line.
|
||||
const char *end = line_start;
|
||||
for (; end < line_end && *end != ';'; ++ end);
|
||||
// Find the 'F' word.
|
||||
const char *fpos = strstr(line_start + 2, " F") + 2;
|
||||
int new_feedrate = current_feedrate;
|
||||
// Modify the F word of the current G-code line.
|
||||
bool modify = false;
|
||||
// Remove the F word from the current G-code line.
|
||||
bool remove = false;
|
||||
assert(fpos != nullptr);
|
||||
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
|
||||
end = line_end;
|
||||
else
|
||||
// Remove the feedrate from the G0/G1 line. The G-code line may become empty!
|
||||
remove = true;
|
||||
} else if (line->slowdown) {
|
||||
// The F value will be overwritten.
|
||||
modify = true;
|
||||
} else {
|
||||
// The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified.
|
||||
// Emit the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
}
|
||||
if (modify || remove) {
|
||||
if (modify) {
|
||||
// Replace the feedrate.
|
||||
new_gcode.append(line_start, fpos - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", int(current_feedrate));
|
||||
new_gcode += buf;
|
||||
} else {
|
||||
// Remove the feedrate word.
|
||||
const char *f = fpos;
|
||||
// Roll the pointer before the 'F' word.
|
||||
for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
|
||||
|
||||
if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) {
|
||||
// BBS: only remain "G1" or "G0" of this line after remove 'F' part, don't save
|
||||
} else {
|
||||
// Append up to the F word, without the trailing whitespace.
|
||||
new_gcode.append(line_start, f - line_start + 1);
|
||||
}
|
||||
}
|
||||
// Skip the non-whitespaces of the F parameter up the comment or end of line.
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (fpos < end)
|
||||
// The G-code line is not empty yet. Emit the rest of it.
|
||||
new_gcode.append(fpos, end - fpos);
|
||||
else if (remove && new_gcode == "G1") {
|
||||
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
|
||||
new_gcode.resize(new_gcode.size() - 2);
|
||||
end = line_end;
|
||||
}
|
||||
}
|
||||
// Process the rest of the line.
|
||||
if (end < line_end) {
|
||||
if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
|
||||
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||
std::string comment(end, line_end);
|
||||
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||
if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
|
||||
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||
if (line->type & CoolingLine::TYPE_WIPE)
|
||||
boost::replace_all(comment, ";_WIPE", "");
|
||||
new_gcode += comment;
|
||||
} else {
|
||||
// Just attach the rest of the source line.
|
||||
new_gcode.append(end, line_end - end);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
}
|
||||
pos = line_end;
|
||||
}
|
||||
const char *gcode_end = gcode.c_str() + gcode.size();
|
||||
if (pos < gcode_end)
|
||||
new_gcode.append(pos, gcode_end - pos);
|
||||
|
||||
return new_gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
|
@ -0,0 +1,294 @@
|
|||
#ifndef slic3r_GCodeEditer_hpp_
|
||||
#define slic3r_GCodeEditer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cfloat>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Slicing.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_OVERHANG_FAN_START = 1 << 2,
|
||||
TYPE_OVERHANG_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
// BBS: add G2 G3 type
|
||||
TYPE_G2 = 1 << 12,
|
||||
TYPE_G3 = 1 << 13,
|
||||
TYPE_FORCE_RESUME_FAN = 1 << 14,
|
||||
TYPE_SET_FAN_CHANGING_LAYER = 1 << 15,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end)
|
||||
: type(type), line_start(line_start), line_end(line_end), length(0.f), feedrate(0.f), origin_feedrate(0.f), time(0.f), time_max(0.f), slowdown(false)
|
||||
{}
|
||||
|
||||
bool adjustable(bool slowdown_external_perimeters) const
|
||||
{
|
||||
return (this->type & TYPE_ADJUSTABLE) && (!(this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && this->time < this->time_max;
|
||||
}
|
||||
|
||||
bool adjustable() const { return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; }
|
||||
|
||||
size_t type;
|
||||
// Start of this line at the G-code snippet.
|
||||
size_t line_start;
|
||||
// End of this line at the G-code snippet.
|
||||
size_t line_end;
|
||||
// XY Euclidian length of this segment.
|
||||
float length;
|
||||
// Current feedrate, possibly adjusted.
|
||||
float feedrate;
|
||||
// Current duration of this segment.
|
||||
float time;
|
||||
// Maximum duration of this segment.
|
||||
float time_max;
|
||||
// If marked with the "slowdown" flag, the line has been slowed down.
|
||||
bool slowdown;
|
||||
// Current feedrate, possibly adjusted.
|
||||
float origin_feedrate = 0;
|
||||
float origin_time_max = 0;
|
||||
// Current duration of this segment.
|
||||
//float origin_time;
|
||||
bool outwall_smooth_mark = false;
|
||||
int object_id = -1;
|
||||
int cooling_node_id = -1;
|
||||
};
|
||||
|
||||
struct PerExtruderAdjustments
|
||||
{
|
||||
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
||||
float elapsed_time_total() const
|
||||
{
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines) time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// 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;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
return FLT_MAX;
|
||||
else
|
||||
time_total += line.time_max;
|
||||
} else
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the adjustable part of the total time.
|
||||
float adjustable_time(bool slowdown_external_perimeters) const
|
||||
{
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters)) time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the non-adjustable part of the total time.
|
||||
float non_adjustable_time(bool slowdown_external_perimeters) const
|
||||
{
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (!line.adjustable(slowdown_external_perimeters)) time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
|
||||
// Used by both proportional and non-proportional slow down.
|
||||
float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters)
|
||||
{
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
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;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
// Slow down each adjustable G-code line proportionally by a factor.
|
||||
// Used by the proportional slow down.
|
||||
float slow_down_proportional(float factor, bool slowdown_external_perimeters)
|
||||
{
|
||||
assert(factor >= 1.f);
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
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;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
|
||||
// Sort the lines, adjustable first, higher feedrate first.
|
||||
// Used by non-proportional slow down.
|
||||
void sort_lines_by_decreasing_feedrate()
|
||||
{
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
|
||||
bool adj1 = l1.adjustable();
|
||||
bool adj2 = l2.adjustable();
|
||||
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
|
||||
});
|
||||
for (n_lines_adjustable = 0; n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); ++n_lines_adjustable)
|
||||
;
|
||||
time_non_adjustable = 0.f;
|
||||
for (size_t i = n_lines_adjustable; i < lines.size(); ++i) time_non_adjustable += lines[i].time;
|
||||
}
|
||||
|
||||
// Calculate the maximum time stretch when slowing down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const
|
||||
{
|
||||
float time_stretch = 0.f;
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++i) {
|
||||
const CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate) time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
|
||||
}
|
||||
return time_stretch;
|
||||
}
|
||||
|
||||
// Slow down all adjustable lines down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
void slow_down_to_feedrate(float min_feedrate)
|
||||
{
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++i) {
|
||||
CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate) {
|
||||
line.time *= std::max(1.f, line.feedrate / min_feedrate);
|
||||
line.feedrate = min_feedrate;
|
||||
line.slowdown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//collect lines time
|
||||
float collection_line_times_of_extruder() {
|
||||
float times = 0;
|
||||
for (const CoolingLine &line: lines) {
|
||||
times += line.time;
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
// Extruder, for which the G-code will be adjusted.
|
||||
unsigned int extruder_id = 0;
|
||||
// Is the cooling slow down logic enabled for this extruder's material?
|
||||
bool cooling_slow_down_enabled = false;
|
||||
// Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time.
|
||||
float slow_down_layer_time = 0.f;
|
||||
// Minimum print speed allowed for this extruder.
|
||||
float slow_down_min_speed = 0.f;
|
||||
|
||||
// Parsed lines.
|
||||
std::vector<CoolingLine> lines;
|
||||
// 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.
|
||||
float time_non_adjustable = 0;
|
||||
// Current total time for this extruder.
|
||||
float time_total = 0;
|
||||
// Maximum time for this extruder, when the maximum slow down is applied.
|
||||
float time_maximum = 0;
|
||||
|
||||
// Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
|
||||
size_t idx_line_begin = 0;
|
||||
size_t idx_line_end = 0;
|
||||
};
|
||||
// A standalone G-code filter, to control cooling of the print.
|
||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||
// and the print is modified to stretch over a minimum layer time.
|
||||
//
|
||||
// The simple it sounds, the actual implementation is significantly more complex.
|
||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||
// For example, some materials may not like to print too slowly, while with some materials
|
||||
// we may slow down significantly.
|
||||
//
|
||||
class GCodeEditer {
|
||||
public:
|
||||
GCodeEditer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id)
|
||||
{
|
||||
m_current_extruder = extruder_id;
|
||||
m_parse_gcode_extruder = extruder_id;
|
||||
}
|
||||
std::string process_layer(std::string && gcode,
|
||||
const size_t layer_id,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
const std::vector<int> & object_label,
|
||||
const bool flush,
|
||||
const bool spiral_vase);
|
||||
|
||||
// float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string write_layer_gcode(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
private :
|
||||
GCodeEditer& operator=(const GCodeEditer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string & gcode,
|
||||
std::vector<float> & current_pos,
|
||||
const std::vector<int> & object_label,
|
||||
bool spiral_vase,
|
||||
bool join_z_smooth);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// BBS: X,Y,Z,E,F,I,J
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
int m_additional_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
// Printing extruder IDs, zero based.
|
||||
std::vector<unsigned int> m_extruder_ids;
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
unsigned int m_parse_gcode_extruder;
|
||||
//BBS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
//BBS:
|
||||
bool m_set_fan_changing_layer = false;
|
||||
bool m_set_addition_fan_changing_layer = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,224 @@
|
|||
#include "Smoothing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void SmoothCalculator::build_node(std::vector<OutwallCollection> & wall_collection,
|
||||
const std::vector<int> & object_label,
|
||||
const std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
if (per_extruder_adjustments.empty())
|
||||
return;
|
||||
// BBS: update outwall feedrate
|
||||
// update feedrate of outwall after initial cooling process
|
||||
// initial and arrange node collection seq
|
||||
for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) {
|
||||
OutwallCollection object_level;
|
||||
object_level.object_id = object_label[object_idx];
|
||||
wall_collection.push_back(object_level);
|
||||
}
|
||||
|
||||
for (size_t extruder_idx = 0; extruder_idx < per_extruder_adjustments.size(); ++extruder_idx) {
|
||||
const PerExtruderAdjustments &extruder_adjustments = per_extruder_adjustments[extruder_idx];
|
||||
for (size_t line_idx = 0; line_idx < extruder_adjustments.lines.size(); ++line_idx) {
|
||||
const CoolingLine &line = extruder_adjustments.lines[line_idx];
|
||||
if (line.outwall_smooth_mark) {
|
||||
// search node id
|
||||
if (wall_collection[line.object_id].cooling_nodes.count(line.cooling_node_id) == 0) {
|
||||
CoolingNode node;
|
||||
wall_collection[line.object_id].cooling_nodes.emplace(line.cooling_node_id, node);
|
||||
}
|
||||
|
||||
CoolingNode &node = wall_collection[line.object_id].cooling_nodes[line.cooling_node_id];
|
||||
if (line.type & CoolingLine::TYPE_EXTERNAL_PERIMETER) {
|
||||
node.outwall_line.emplace_back(line_idx, extruder_idx);
|
||||
if (node.max_feedrate < line.feedrate) {
|
||||
node.max_feedrate = line.feedrate;
|
||||
node.filter_feedrate = node.max_feedrate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void exclude_participate_in_speed_slowdown(std::vector<std::pair<int, int>> & lines,
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
|
||||
CoolingNode & node)
|
||||
{
|
||||
// BBS: add protect, feedrate will be 0 if the outwall is overhang. just apply not adjust flage
|
||||
bool apply_speed = node.max_feedrate > 0 && node.filter_feedrate > 0;
|
||||
if (apply_speed) node.rate = node.filter_feedrate / node.max_feedrate;
|
||||
|
||||
for (std::pair<int, int> line_pos : lines) {
|
||||
CoolingLine &line = per_extruder_adjustments[line_pos.second].lines[line_pos.first];
|
||||
if (apply_speed && line.feedrate > node.filter_feedrate) {
|
||||
line.feedrate = node.filter_feedrate;
|
||||
line.slowdown = true;
|
||||
}
|
||||
|
||||
// not adjust outwal line speed
|
||||
line.type = line.type & (~CoolingLine::TYPE_ADJUSTABLE);
|
||||
// update time cost
|
||||
if (line.feedrate == 0 || line.length == 0)
|
||||
line.time = 0;
|
||||
else
|
||||
line.time = line.length / line.feedrate;
|
||||
}
|
||||
}
|
||||
|
||||
float SmoothCalculator::recaculate_layer_time(int layer_id, std::vector<PerExtruderAdjustments> &extruder_adjustments)
|
||||
{
|
||||
// rewrite feedrate
|
||||
for (size_t obj_id = 0; obj_id < layers_wall_collection[layer_id].size(); ++obj_id) {
|
||||
for (size_t node_id = 0; node_id < layers_wall_collection[layer_id][obj_id].cooling_nodes.size(); ++node_id) {
|
||||
CoolingNode &node = layers_wall_collection[layer_id][obj_id].cooling_nodes[node_id];
|
||||
// set outwall speed
|
||||
exclude_participate_in_speed_slowdown(node.outwall_line, extruder_adjustments, node);
|
||||
}
|
||||
}
|
||||
|
||||
float layer_time = 0;
|
||||
for (PerExtruderAdjustments extruder : extruder_adjustments) {
|
||||
layer_time += extruder.collection_line_times_of_extruder();
|
||||
}
|
||||
|
||||
return layer_time;
|
||||
};
|
||||
|
||||
void SmoothCalculator::init_object_node_range()
|
||||
{
|
||||
for (size_t object_id = 0; object_id < objects_node_range.size(); ++object_id) {
|
||||
for (size_t layer_id = 1; layer_id < layers_wall_collection.size(); ++layer_id) {
|
||||
const OutwallCollection &each_object = layers_wall_collection[layer_id][object_id];
|
||||
auto it = each_object.cooling_nodes.begin();
|
||||
while (it != each_object.cooling_nodes.end()) {
|
||||
if (objects_node_range[object_id].count(it->first) == 0) {
|
||||
objects_node_range[object_id].emplace(it->first, std::pair<int, int>(layer_id, layer_id));
|
||||
} else {
|
||||
objects_node_range[object_id][it->first].second = layer_id;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothCalculator::smooth_layer_speed()
|
||||
{
|
||||
init_object_node_range();
|
||||
|
||||
for (size_t obj_id = 0; obj_id < objects_node_range.size(); ++obj_id) {
|
||||
auto it = objects_node_range[obj_id].begin();
|
||||
while (it != objects_node_range[obj_id].end()) {
|
||||
int step_count = 0;
|
||||
while (step_count < max_steps_count && speed_filter_continue(obj_id, it->first)) {
|
||||
step_count++;
|
||||
layer_speed_filter(obj_id, it->first);
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothCalculator::layer_speed_filter(const int object_id, const int node_id)
|
||||
{
|
||||
int start_pos = guassian_filter.size() / 2;
|
||||
// first layer don't need to be smoothed
|
||||
int layer_start = objects_node_range[object_id][node_id].first;
|
||||
int layer_end = objects_node_range[object_id][node_id].second;
|
||||
|
||||
// BBS: some layers may empty as the support has indenpendent layer
|
||||
for (int layer_id = layer_start; layer_id <= layer_end; ++layer_id) {
|
||||
if (layers_wall_collection[layer_id].empty()) continue;
|
||||
|
||||
if (layers_wall_collection[layer_id][object_id].cooling_nodes.count(node_id) == 0) break;
|
||||
|
||||
CoolingNode &node = layers_wall_collection[layer_id][object_id].cooling_nodes[node_id];
|
||||
|
||||
if (node.outwall_line.empty()) continue;
|
||||
|
||||
double conv_sum = 0;
|
||||
for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
|
||||
int remap_data_pos = layer_id - start_pos + filter_pos_idx;
|
||||
|
||||
if (remap_data_pos < layer_start)
|
||||
remap_data_pos = layer_start;
|
||||
else if (remap_data_pos > layer_end)
|
||||
remap_data_pos = layer_end;
|
||||
|
||||
// some node may not start at layer 1
|
||||
double remap_data = node.filter_feedrate;
|
||||
if (!layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].outwall_line.empty())
|
||||
remap_data = layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].filter_feedrate;
|
||||
|
||||
conv_sum += guassian_filter[filter_pos_idx] * remap_data;
|
||||
}
|
||||
double filter_res = conv_sum / filter_sum;
|
||||
if (filter_res < node.filter_feedrate) node.filter_feedrate = filter_res;
|
||||
}
|
||||
}
|
||||
|
||||
bool SmoothCalculator::speed_filter_continue(const int object_id, const int node_id)
|
||||
{
|
||||
int layer_id = objects_node_range[object_id][node_id].first;
|
||||
int layer_end = objects_node_range[object_id][node_id].second;
|
||||
|
||||
// BBS: some layers may empty as the support has indenpendent layer
|
||||
for (; layer_id < layer_end; ++layer_id) {
|
||||
if (std::abs(layers_wall_collection[layer_id + 1][object_id].cooling_nodes[node_id].filter_feedrate -
|
||||
layers_wall_collection[layer_id][object_id].cooling_nodes[node_id].filter_feedrate) > guassian_stop_threshold)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SmoothCalculator::filter_layer_time()
|
||||
{
|
||||
int start_pos = guassian_filter.size() / 2;
|
||||
// first layer don't need to be smoothed
|
||||
for (int layer_id = 1; layer_id < layers_cooling_time.size(); ++layer_id) {
|
||||
if (layers_cooling_time[layer_id] > layer_time_smoothing_threshold) continue;
|
||||
|
||||
double conv_sum = 0;
|
||||
for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
|
||||
int remap_data_pos = layer_id - start_pos + filter_pos_idx;
|
||||
|
||||
if (remap_data_pos < 1)
|
||||
remap_data_pos = 1;
|
||||
else if (remap_data_pos > layers_cooling_time.size() - 1)
|
||||
remap_data_pos = layers_cooling_time.size() - 1;
|
||||
|
||||
// if the layer time big enough, surface defact will disappear
|
||||
double data_temp = layers_cooling_time[remap_data_pos] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[remap_data_pos];
|
||||
|
||||
conv_sum += guassian_filter[filter_pos_idx] * data_temp;
|
||||
}
|
||||
double filter_res = conv_sum / filter_sum;
|
||||
filter_res = filter_res > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : filter_res;
|
||||
if (filter_res > layers_cooling_time[layer_id]) layers_cooling_time[layer_id] = filter_res;
|
||||
}
|
||||
}
|
||||
|
||||
bool SmoothCalculator::layer_time_filter_continue()
|
||||
{
|
||||
for (int layer_id = 1; layer_id < layers_cooling_time.size() - 1; ++layer_id) {
|
||||
double layer_time = layers_cooling_time[layer_id] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id];
|
||||
double layer_time_cmp = layers_cooling_time[layer_id + 1] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id + 1];
|
||||
|
||||
if (std::abs(layer_time - layer_time_cmp) > guassian_layer_time_stop_threshold) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SmoothCalculator::smooth_layer_time()
|
||||
{
|
||||
int step_count = 0;
|
||||
while (step_count < max_steps_count && layer_time_filter_continue()) {
|
||||
step_count++;
|
||||
filter_layer_time();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
|
@ -0,0 +1,103 @@
|
|||
#ifndef slic3r_Smoothing_hpp_
|
||||
#define slic3r_Smoothing_hpp_
|
||||
#include "../libslic3r.h"
|
||||
#include <libslic3r/GCode/GCodeEditer.hpp>
|
||||
#include <math.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static const int guassian_window_size = 11;
|
||||
static const int guassian_r = 2;
|
||||
static const int guassian_stop_threshold = 5;
|
||||
static const float guassian_layer_time_stop_threshold = 3.0;
|
||||
static const int max_steps_count = 1000;
|
||||
|
||||
struct CoolingNode
|
||||
{
|
||||
// extruder pos, line pos;
|
||||
std::vector<std::pair<int, int>> outwall_line;
|
||||
float max_feedrate = 0;
|
||||
float filter_feedrate = 0;
|
||||
double rate = 1;
|
||||
};
|
||||
|
||||
struct OutwallCollection
|
||||
{
|
||||
int object_id;
|
||||
std::map<int, CoolingNode> cooling_nodes;
|
||||
};
|
||||
|
||||
class SmoothCalculator
|
||||
{
|
||||
|
||||
public:
|
||||
std::vector<std::map<int, std::pair<int, int>>> objects_node_range;
|
||||
std::vector<std::vector<OutwallCollection>> layers_wall_collection;
|
||||
std::vector<float> layers_cooling_time;
|
||||
|
||||
SmoothCalculator(const int objects_size, const double gap_limit) : layer_time_smoothing_threshold(gap_limit)
|
||||
{
|
||||
guassian_filter_generator();
|
||||
objects_node_range.resize(objects_size);
|
||||
}
|
||||
|
||||
SmoothCalculator(const int objects_size)
|
||||
{
|
||||
guassian_filter_generator();
|
||||
objects_node_range.resize(objects_size);
|
||||
}
|
||||
|
||||
void append_data(const std::vector<OutwallCollection> &wall_collection, float cooling_time)
|
||||
{
|
||||
layers_wall_collection.push_back(wall_collection);
|
||||
layers_cooling_time.push_back(cooling_time);
|
||||
}
|
||||
|
||||
void append_data(const std::vector<OutwallCollection> &wall_collection)
|
||||
{
|
||||
layers_wall_collection.push_back(wall_collection);
|
||||
}
|
||||
|
||||
void build_node(std::vector<OutwallCollection> &wall_collection, const std::vector<int> &object_label, const std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
float recaculate_layer_time(int layer_id, std::vector<PerExtruderAdjustments> &extruder_adjustments);
|
||||
|
||||
void smooth_layer_speed();
|
||||
|
||||
private:
|
||||
// guassian filter
|
||||
double guassian_function(double x, double r) {
|
||||
return exp(-x * x / (2 * r * r)) / (r * sqrt(2 * PI));
|
||||
}
|
||||
|
||||
void guassian_filter_generator() {
|
||||
double r = guassian_r;
|
||||
int half_win_size = guassian_window_size / 2;
|
||||
for (int start = -half_win_size; start <= half_win_size; ++start) {
|
||||
double y = guassian_function(start, r);
|
||||
filter_sum += y;
|
||||
guassian_filter.push_back(y);
|
||||
}
|
||||
}
|
||||
|
||||
void init_object_node_range();
|
||||
|
||||
// filter the data
|
||||
void layer_speed_filter(const int object_id, const int node_id);
|
||||
|
||||
bool speed_filter_continue(const int object_id, const int node_id);
|
||||
|
||||
// filter the data
|
||||
void filter_layer_time();
|
||||
|
||||
bool layer_time_filter_continue();
|
||||
void smooth_layer_time();
|
||||
|
||||
std::vector<double> guassian_filter;
|
||||
double filter_sum = .0f;
|
||||
float layer_time_smoothing_threshold = 30.0f;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
|
@ -5,8 +5,10 @@
|
|||
#include "ShortestPath.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
static const int Continuitious_length = scale_(0.01);
|
||||
static const int dist_scale_threshold = 1.2;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -195,7 +197,7 @@ void Layer::make_perimeters()
|
|||
|
||||
if (layerms.size() == 1) { // optimization
|
||||
(*layerm)->fill_surfaces.surfaces.clear();
|
||||
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons);
|
||||
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes);
|
||||
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
|
||||
} else {
|
||||
SurfaceCollection new_slices;
|
||||
|
@ -219,7 +221,7 @@ void Layer::make_perimeters()
|
|||
SurfaceCollection fill_surfaces;
|
||||
//BBS
|
||||
ExPolygons fill_no_overlap;
|
||||
layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap);
|
||||
layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap, this->loop_nodes);
|
||||
|
||||
// assign fill_surfaces to each layer
|
||||
if (!fill_surfaces.surfaces.empty()) {
|
||||
|
@ -234,9 +236,148 @@ void Layer::make_perimeters()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
|
||||
}
|
||||
|
||||
//BBS: use aabbtree to get distance
|
||||
class ContinuitiousDistancer
|
||||
{
|
||||
std::vector<Linef> lines;
|
||||
AABBTreeIndirect::Tree<2, double> tree;
|
||||
|
||||
public:
|
||||
ContinuitiousDistancer(const Points &pts)
|
||||
{
|
||||
Lines pt_to_lines = to_lines(pts);
|
||||
for (const auto &line : pt_to_lines)
|
||||
lines.emplace_back(line.a.cast<double>(), line.b.cast<double>());
|
||||
|
||||
tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
|
||||
}
|
||||
|
||||
float distance_from_perimeter(const Vec2f &point) const
|
||||
{
|
||||
Vec2d p = point.cast<double>();
|
||||
size_t hit_idx_out{};
|
||||
Vec2d hit_point_out = Vec2d::Zero();
|
||||
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, hit_idx_out, hit_point_out);
|
||||
if (distance < 0) {
|
||||
return std::numeric_limits<float>::max();
|
||||
}
|
||||
|
||||
distance = sqrt(distance);
|
||||
const Linef &line = lines[hit_idx_out];
|
||||
Vec2d v1 = line.b - line.a;
|
||||
Vec2d v2 = p - line.a;
|
||||
if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { distance *= -1; }
|
||||
return distance;
|
||||
}
|
||||
|
||||
Lines to_lines(const Points &pts)
|
||||
{
|
||||
Lines lines;
|
||||
if (pts.size() >= 2) {
|
||||
lines.reserve(pts.size() - 1);
|
||||
for (Points::const_iterator it = pts.begin(); it != pts.end() - 1; ++it) { lines.push_back(Line(*it, *(it + 1))); }
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static double get_node_continuity_rang_limit(const std::vector<coord_t> &Prev_node_widths, int prev_pt_idx) {
|
||||
double width = 0;
|
||||
double prev_width = Prev_node_widths.front();
|
||||
if (prev_pt_idx!=0 && prev_pt_idx < Prev_node_widths.size()) {
|
||||
prev_width = static_cast<double>(Prev_node_widths[prev_pt_idx]);
|
||||
}
|
||||
|
||||
width = prev_width * dist_scale_threshold;
|
||||
return width;
|
||||
}
|
||||
|
||||
void Layer::calculate_perimeter_continuity(std::vector<LoopNode> &prev_nodes) {
|
||||
for (size_t node_pos = 0; node_pos < loop_nodes.size(); ++node_pos) {
|
||||
LoopNode &node=loop_nodes[node_pos];
|
||||
double width = 0;
|
||||
ContinuitiousDistancer node_distancer(node.node_contour.pts);
|
||||
for (size_t prev_pos = 0; prev_pos < prev_nodes.size(); ++prev_pos) {
|
||||
LoopNode &prev_node = prev_nodes[prev_pos];
|
||||
|
||||
// no overlap or has diff speed
|
||||
if (!node.bbox.overlap(prev_node.bbox))
|
||||
continue;
|
||||
|
||||
//calculate dist, checkout the continuity
|
||||
Polyline continuitious_pl;
|
||||
//check start pt
|
||||
size_t start = 0;
|
||||
bool conntiouitious_flag = false;
|
||||
int end = prev_node.node_contour.pts.size() - 1;
|
||||
|
||||
//if the countor is loop
|
||||
if (prev_node.node_contour.is_loop) {
|
||||
for (; end >= 0; --end) {
|
||||
if (continuitious_pl.length() >= Continuitious_length) {
|
||||
node.lower_node_id.push_back(prev_node.node_id);
|
||||
prev_node.upper_node_id.push_back(node.node_id);
|
||||
conntiouitious_flag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Point pt = prev_node.node_contour.pts[end];
|
||||
float dist = node_distancer.distance_from_perimeter(pt.cast<float>());
|
||||
// get corr width
|
||||
width = get_node_continuity_rang_limit(prev_node.node_contour.widths, end);
|
||||
|
||||
if (dist < width && dist > -width)
|
||||
continuitious_pl.append_before(pt);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (conntiouitious_flag || end < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
int last_pt_idx = end;
|
||||
// line need to check end point
|
||||
if (!prev_node.node_contour.is_loop)
|
||||
last_pt_idx ++;
|
||||
|
||||
for (; start < last_pt_idx; ++start) {
|
||||
Point pt = prev_node.node_contour.pts[start];
|
||||
float dist = node_distancer.distance_from_perimeter(pt.cast<float>());
|
||||
//get corr width
|
||||
width = get_node_continuity_rang_limit(prev_node.node_contour.widths, start);
|
||||
|
||||
if (dist < width && dist > -width) {
|
||||
continuitious_pl.append(pt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (continuitious_pl.empty() || continuitious_pl.length() < Continuitious_length) {
|
||||
continuitious_pl.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
node.lower_node_id.push_back(prev_node.node_id);
|
||||
prev_node.upper_node_id.push_back(node.node_id);
|
||||
continuitious_pl.clear();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (continuitious_pl.length() >= Continuitious_length) {
|
||||
node.lower_node_id.push_back(prev_node.node_id);
|
||||
prev_node.upper_node_id.push_back(node.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Layer::export_region_slices_to_svg(const char *path) const
|
||||
{
|
||||
BoundingBox bbox;
|
||||
|
|
|
@ -80,7 +80,7 @@ public:
|
|||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
//BBS
|
||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap);
|
||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap, std::vector<LoopNode> &loop_nodes);
|
||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||
double infill_area_threshold() const;
|
||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||
|
@ -155,6 +155,7 @@ public:
|
|||
// BBS
|
||||
ExPolygons loverhangs;
|
||||
BoundingBox loverhangs_bbox;
|
||||
std::vector<LoopNode> loop_nodes;
|
||||
size_t region_count() const { return m_regions.size(); }
|
||||
const LayerRegion* get_region(int idx) const { return m_regions[idx]; }
|
||||
LayerRegion* get_region(int idx) { return m_regions[idx]; }
|
||||
|
@ -180,6 +181,9 @@ public:
|
|||
return false;
|
||||
}
|
||||
void make_perimeters();
|
||||
//BBS
|
||||
void calculate_perimeter_continuity(std::vector<LoopNode> &prev_nodes);
|
||||
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
|
||||
|
|
|
@ -64,7 +64,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||
}
|
||||
}
|
||||
|
||||
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap)
|
||||
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector<LoopNode> &loop_nodes)
|
||||
{
|
||||
this->perimeters.clear();
|
||||
this->thin_fills.clear();
|
||||
|
@ -93,7 +93,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
|
|||
&this->thin_fills,
|
||||
fill_surfaces,
|
||||
//BBS
|
||||
fill_no_overlap
|
||||
fill_no_overlap,
|
||||
&loop_nodes
|
||||
);
|
||||
|
||||
if (this->layer()->lower_layer != nullptr)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Clipper2Utils.hpp"
|
||||
#include "Arachne/WallToolPaths.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <random>
|
||||
|
@ -887,13 +888,32 @@ static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_gener
|
|||
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector<PerimeterGeneratorArachneExtrusion>& pg_extrusions)
|
||||
{
|
||||
ExtrusionEntityCollection extrusion_coll;
|
||||
for (PerimeterGeneratorArachneExtrusion& pg_extrusion : pg_extrusions) {
|
||||
if (perimeter_generator.print_config->z_direction_outwall_speed_continuous)
|
||||
extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size();
|
||||
|
||||
for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) {
|
||||
Arachne::ExtrusionLine* extrusion = pg_extrusion.extrusion;
|
||||
if (extrusion->empty())
|
||||
continue;
|
||||
|
||||
//get extrusion date
|
||||
const bool is_external = extrusion->inset_idx == 0;
|
||||
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
|
||||
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
|
||||
|
||||
if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) {
|
||||
LoopNode node;
|
||||
NodeContour node_contour;
|
||||
node_contour.is_loop = extrusion->is_closed;
|
||||
for (size_t i = 0; i < extrusion->junctions.size(); i++) {
|
||||
node_contour.pts.push_back(extrusion->junctions[i].p);
|
||||
node_contour.widths.push_back(extrusion->junctions[i].w);
|
||||
}
|
||||
node.node_contour = node_contour;
|
||||
node.node_id = perimeter_generator.loop_nodes->size();
|
||||
node.loop_id = extrusion_coll.entities.size();
|
||||
node.bbox = get_extents(node.node_contour.pts);
|
||||
node.bbox.offset(perimeter_generator.config->outer_wall_line_width/2);
|
||||
perimeter_generator.loop_nodes->push_back(std::move(node));
|
||||
}
|
||||
|
||||
if (pg_extrusion.fuzzify)
|
||||
fuzzy_extrusion_line(*extrusion, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_distance.value));
|
||||
|
@ -1084,7 +1104,10 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
|||
extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (perimeter_generator.print_config->z_direction_outwall_speed_continuous)
|
||||
extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size();
|
||||
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
@ -1166,6 +1189,7 @@ void PerimeterGenerator::process_classic()
|
|||
ExPolygons gaps;
|
||||
ExPolygons top_fills;
|
||||
ExPolygons fill_clip;
|
||||
std::vector<NodeContour> outwall_paths;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
|
@ -1291,6 +1315,35 @@ void PerimeterGenerator::process_classic()
|
|||
|
||||
//BBS: save perimeter loop which use smaller width
|
||||
if (i == 0) {
|
||||
//store outer wall
|
||||
if (print_config->z_direction_outwall_speed_continuous) {
|
||||
// not loop
|
||||
for (const ThickPolyline &polyline : thin_walls) {
|
||||
NodeContour node_contour;
|
||||
node_contour.is_loop = false;
|
||||
node_contour.pts = polyline.points;
|
||||
node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end());
|
||||
outwall_paths.push_back(node_contour);
|
||||
}
|
||||
|
||||
// loop
|
||||
for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) {
|
||||
NodeContour node_contour;
|
||||
node_contour.is_loop = true;
|
||||
node_contour.pts = polyline.points;
|
||||
node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width()));
|
||||
outwall_paths.push_back(node_contour);
|
||||
}
|
||||
|
||||
for (const Polyline &polyline : to_polylines(offsets)) {
|
||||
NodeContour node_contour;
|
||||
node_contour.is_loop = true;
|
||||
node_contour.pts = polyline.points;
|
||||
node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width()));
|
||||
outwall_paths.push_back(node_contour);
|
||||
}
|
||||
}
|
||||
|
||||
for (const ExPolygon& expolygon : offsets_with_smaller_width) {
|
||||
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true));
|
||||
if (!expolygon.holes.empty()) {
|
||||
|
@ -1467,6 +1520,48 @@ void PerimeterGenerator::process_classic()
|
|||
}
|
||||
entities.entities = std::move( entities_reorder);
|
||||
}
|
||||
|
||||
//BBS: add node for loops
|
||||
if (!outwall_paths.empty() && this->layer_id > 0) {
|
||||
entities.loop_node_range.first = this->loop_nodes->size();
|
||||
if (outwall_paths.size() == 1) {
|
||||
LoopNode node;
|
||||
node.node_id = this->loop_nodes->size();
|
||||
node.loop_id = 0;
|
||||
node.node_contour = outwall_paths.front();
|
||||
node.bbox = get_extents(node.node_contour.pts);
|
||||
node.bbox.offset(SCALED_EPSILON);
|
||||
this->loop_nodes->push_back(node);
|
||||
} else {
|
||||
std::vector<bool> matched;
|
||||
matched.resize(outwall_paths.size(), false);
|
||||
for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) {
|
||||
//skip inner wall
|
||||
if(entities.entities[entity_idx]->role() == erPerimeter)
|
||||
continue;
|
||||
|
||||
for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) {
|
||||
if(matched[lines_idx])
|
||||
continue;
|
||||
|
||||
if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) {
|
||||
matched[lines_idx] = true;
|
||||
LoopNode node;
|
||||
node.node_id = this->loop_nodes->size();
|
||||
node.loop_id = entity_idx;
|
||||
node.node_contour = outwall_paths[lines_idx];
|
||||
node.bbox = get_extents(node.node_contour.pts);
|
||||
node.bbox.offset(SCALED_EPSILON);
|
||||
this->loop_nodes->push_back(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entities.loop_node_range.second = this->loop_nodes->size();
|
||||
}
|
||||
|
||||
|
||||
// append perimeters for this slice as a collection
|
||||
if (! entities.empty())
|
||||
this->loops->append(entities);
|
||||
|
|
|
@ -40,8 +40,8 @@ public:
|
|||
std::pair<double, double> m_lower_overhang_dist_boundary;
|
||||
std::pair<double, double> m_external_overhang_dist_boundary;
|
||||
std::pair<double, double> m_smaller_external_overhang_dist_boundary;
|
||||
std::vector<LoopNode> *loop_nodes;
|
||||
|
||||
|
||||
PerimeterGenerator(
|
||||
// Input:
|
||||
const SurfaceCollection* slices,
|
||||
|
@ -59,14 +59,18 @@ public:
|
|||
// Infills without the gap fills
|
||||
SurfaceCollection* fill_surfaces,
|
||||
//BBS
|
||||
ExPolygons* fill_no_overlap)
|
||||
ExPolygons* fill_no_overlap,
|
||||
std::vector<LoopNode> *loop_nodes)
|
||||
: slices(slices), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height),
|
||||
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
||||
overhang_flow(flow), solid_infill_flow(flow),
|
||||
config(config), object_config(object_config), print_config(print_config),
|
||||
m_spiral_vase(spiral_mode),
|
||||
m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)),
|
||||
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap),
|
||||
m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), loops(loops),
|
||||
gap_fill(gap_fill),
|
||||
fill_surfaces(fill_surfaces),
|
||||
fill_no_overlap(fill_no_overlap),
|
||||
loop_nodes(loop_nodes),
|
||||
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1)
|
||||
{}
|
||||
|
||||
|
|
|
@ -179,6 +179,49 @@ Point Point::projection_onto(const Line &line) const
|
|||
return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b;
|
||||
}
|
||||
|
||||
|
||||
bool Point::is_in_lines(const Points &pts) const
|
||||
{
|
||||
const Point check_point = *this;
|
||||
for (int pt_idx = 1; pt_idx < pts.size(); pt_idx++) {
|
||||
const Point pt = pts[pt_idx];
|
||||
const Point prev_pt = pts[pt_idx - 1];
|
||||
|
||||
// if on the endpoints
|
||||
if ((check_point.x() == pt.x() && check_point.y() == pt.y()) || (check_point.x() == prev_pt.x() && check_point.y() == prev_pt.y()))
|
||||
return true;
|
||||
|
||||
bool in_x_range = !(check_point.x() > pt.x() == check_point.x() > prev_pt.x());
|
||||
bool in_y_range = !(check_point.y() > pt.y() == check_point.y() > prev_pt.y());
|
||||
|
||||
//on vert line
|
||||
if (pt.x() == prev_pt.x()) {
|
||||
if (in_y_range && pt.x() == check_point.x())
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// on hori line
|
||||
if (pt.y() == prev_pt.y()) {
|
||||
if (in_x_range && pt.y() == check_point.y())
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
//not right range
|
||||
if (!in_x_range || !in_y_range)
|
||||
continue;
|
||||
|
||||
// check if in line
|
||||
Line line(prev_pt, pt);
|
||||
double distance = line.distance_to(*this);
|
||||
if (std::abs(distance) < SCALED_EPSILON)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_duplicate_points(std::vector<Point> &&pts)
|
||||
{
|
||||
std::sort(pts.begin(), pts.end());
|
||||
|
|
|
@ -236,6 +236,7 @@ public:
|
|||
double ccw_angle(const Point &p1, const Point &p2) const;
|
||||
Point projection_onto(const MultiPoint &poly) const;
|
||||
Point projection_onto(const Line &line) const;
|
||||
bool is_in_lines(const Points &pts) const;
|
||||
};
|
||||
|
||||
inline bool operator<(const Point &l, const Point &r)
|
||||
|
|
|
@ -895,7 +895,7 @@ static std::vector<std::string> s_Preset_print_options {
|
|||
"seam_gap", "wipe_speed", "top_solid_infill_flow_ratio", "initial_layer_flow_ratio",
|
||||
"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk",
|
||||
"filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth",
|
||||
"small_perimeter_speed", "small_perimeter_threshold",
|
||||
"small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous",
|
||||
// calib
|
||||
"print_flow_ratio",
|
||||
//Orca
|
||||
|
|
|
@ -296,7 +296,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||
//|| opt_key == "resolution"
|
||||
//BBS: when enable arc fitting, we must re-generate perimeter
|
||||
|| opt_key == "enable_arc_fitting"
|
||||
|| opt_key == "wall_sequence") {
|
||||
|| opt_key == "wall_sequence"
|
||||
|| opt_key == "z_direction_outwall_speed_continuous") {
|
||||
osteps.emplace_back(posPerimeters);
|
||||
osteps.emplace_back(posInfill);
|
||||
osteps.emplace_back(posSupportMaterial);
|
||||
|
|
|
@ -507,7 +507,7 @@ private:
|
|||
|
||||
// BBS
|
||||
SupportNecessaryType is_support_necessary();
|
||||
|
||||
void merge_layer_node(const size_t layer_id, int &max_merged_id, std::map<int, std::vector<std::pair<int, int>>> &node_record);
|
||||
// XYZ in scaled coordinates
|
||||
Vec3crd m_size;
|
||||
double m_max_z;
|
||||
|
|
|
@ -678,6 +678,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("z_direction_outwall_speed_continuous", coBool);
|
||||
def->label = L("Z direction outwall speed continuous");
|
||||
def->category = L("Quality");
|
||||
def->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("max_travel_detour_distance", coFloatOrPercent);
|
||||
def->label = L("Avoid crossing wall - Max detour length");
|
||||
def->category = L("Quality");
|
||||
|
|
|
@ -1084,6 +1084,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||
//BBS
|
||||
((ConfigOptionInts, additional_cooling_fan_speed))
|
||||
((ConfigOptionBool, reduce_crossing_wall))
|
||||
((ConfigOptionBool, z_direction_outwall_speed_continuous))
|
||||
((ConfigOptionFloatOrPercent, max_travel_detour_distance))
|
||||
((ConfigOptionPoints, printable_area))
|
||||
((ConfigOptionPointsGroups, extruder_printable_area))
|
||||
|
|
|
@ -129,6 +129,67 @@ std::vector<std::reference_wrapper<const PrintRegion>> PrintObject::all_regions(
|
|||
return out;
|
||||
}
|
||||
|
||||
void PrintObject::merge_layer_node(const size_t layer_id, int &max_merged_id, std::map<int, std::vector<std::pair<int, int>>> &node_record)
|
||||
{
|
||||
Layer *this_layer = m_layers[layer_id];
|
||||
std::vector<LoopNode> &loop_nodes = this_layer->loop_nodes;
|
||||
for (size_t idx = 0; idx < loop_nodes.size(); ++idx) {
|
||||
//new cool node
|
||||
if (loop_nodes[idx].lower_node_id.empty()) {
|
||||
max_merged_id++;
|
||||
loop_nodes[idx].merged_id = max_merged_id;
|
||||
std::vector<std::pair<int, int>> node_pos;
|
||||
node_pos.emplace_back(layer_id, idx);
|
||||
node_record.emplace(max_merged_id, node_pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
//it should finds key in map
|
||||
if (loop_nodes[idx].lower_node_id.size() == 1) {
|
||||
loop_nodes[idx].merged_id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id.front()].merged_id;
|
||||
node_record[loop_nodes[idx].merged_id].emplace_back(layer_id, idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
//min index
|
||||
int min_merged_id = -1;
|
||||
std::vector<int> appear_id;
|
||||
for (size_t lower_idx = 0; lower_idx < loop_nodes[idx].lower_node_id.size(); ++lower_idx) {
|
||||
int id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id[lower_idx]].merged_id;
|
||||
if (min_merged_id == -1 || min_merged_id > id)
|
||||
min_merged_id = id;
|
||||
appear_id.push_back(id);
|
||||
}
|
||||
|
||||
loop_nodes[idx].merged_id = min_merged_id;
|
||||
node_record[min_merged_id].emplace_back(layer_id, idx);
|
||||
|
||||
//update other node merged id
|
||||
for (size_t appear_node_idx = 0; appear_node_idx < appear_id.size(); ++appear_node_idx) {
|
||||
if (appear_id[appear_node_idx] == min_merged_id)
|
||||
continue;
|
||||
|
||||
auto it = node_record.find(appear_id[appear_node_idx]);
|
||||
//protect
|
||||
if (it == node_record.end())
|
||||
continue;
|
||||
|
||||
std::vector<std::pair<int, int>> &appear_node_pos = it->second;
|
||||
|
||||
for (size_t node_idx = 0; node_idx < appear_node_pos.size(); ++node_idx) {
|
||||
int node_layer = appear_node_pos[node_idx].first;
|
||||
int node_pos = appear_node_pos[node_idx].second;
|
||||
|
||||
LoopNode &node = m_layers[node_layer]->loop_nodes[node_pos];
|
||||
|
||||
node.merged_id = min_merged_id;
|
||||
node_record[min_merged_id].emplace_back(node_layer, node_pos);
|
||||
}
|
||||
node_record.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Merges typed region slices into stInternal type.
|
||||
// 2) Increases an "extra perimeters" counter at region slices where needed.
|
||||
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
|
||||
|
@ -236,6 +297,33 @@ void PrintObject::make_perimeters()
|
|||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
|
||||
|
||||
if (this->m_print->m_config.z_direction_outwall_speed_continuous) {
|
||||
// BBS: get continuity of nodes
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - start";
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
if (layer_idx > 1) {
|
||||
Layer &prev_layer = *m_layers[layer_idx - 1];
|
||||
m_layers[layer_idx]->calculate_perimeter_continuity(m_layers[layer_idx - 1]->loop_nodes);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - end";
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - start";
|
||||
|
||||
int max_merged_id = -1;
|
||||
std::map<int,std::vector<std::pair<int, int>>> node_record;
|
||||
for (size_t layer_idx = 1; layer_idx < m_layers.size(); ++layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
merge_layer_node(layer_idx, max_merged_id, node_record);
|
||||
}
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - end";
|
||||
}
|
||||
this->set_done(posPerimeters);
|
||||
}
|
||||
|
||||
|
|
|
@ -2041,6 +2041,7 @@ void TabPrint::build()
|
|||
optgroup->append_single_option_line("smooth_coefficient","parameter/quality-advance-settings");
|
||||
optgroup->append_single_option_line("reduce_crossing_wall","parameter/quality-advance-settings");
|
||||
optgroup->append_single_option_line("max_travel_detour_distance","parameter/quality-advance-settings");
|
||||
optgroup->append_single_option_line("z_direction_outwall_speed_continuous", "parameter/quality-advance-settings");
|
||||
|
||||
page = add_options_page(L("Strength"), "empty");
|
||||
optgroup = page->new_optgroup(L("Walls"), L"param_wall");
|
||||
|
|
Loading…
Reference in New Issue