WIP Refactoring of Layers: Reworked G-code export to make use
of Layer->LayerSlice->LayerIsland hierarchy. This should improve tool path ordering of multiple parts within the same object #5511. Some shells tests rewritten from Perl to C++. FIXME: Gap fill extrusions are currently not handled by the initial G-code preview!
This commit is contained in:
parent
6e653d9070
commit
8858651bf4
@ -127,12 +127,12 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
void collect_polylines(Polylines &dst) const override {
|
void collect_polylines(Polylines &dst) const override {
|
||||||
for (ExtrusionEntity* extrusion_entity : this->entities)
|
for (const ExtrusionEntity *extrusion_entity : this->entities)
|
||||||
extrusion_entity->collect_polylines(dst);
|
extrusion_entity->collect_polylines(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_points(Points &dst) const override {
|
void collect_points(Points &dst) const override {
|
||||||
for (ExtrusionEntity* extrusion_entity : this->entities)
|
for (const ExtrusionEntity *extrusion_entity : this->entities)
|
||||||
extrusion_entity->collect_points(dst);
|
extrusion_entity->collect_points(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,9 +475,9 @@ namespace Slic3r {
|
|||||||
|
|
||||||
// Collect pairs of object_layer + support_layer sorted by print_z.
|
// Collect pairs of object_layer + support_layer sorted by print_z.
|
||||||
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
|
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
|
||||||
std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject& object)
|
GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object)
|
||||||
{
|
{
|
||||||
std::vector<GCode::LayerToPrint> layers_to_print;
|
GCode::ObjectsLayerToPrint layers_to_print;
|
||||||
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
|
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -501,9 +501,9 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
|||||||
// Pair the object layers with the support layers by z.
|
// Pair the object layers with the support layers by z.
|
||||||
size_t idx_object_layer = 0;
|
size_t idx_object_layer = 0;
|
||||||
size_t idx_support_layer = 0;
|
size_t idx_support_layer = 0;
|
||||||
const LayerToPrint* last_extrusion_layer = nullptr;
|
const ObjectLayerToPrint* last_extrusion_layer = nullptr;
|
||||||
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
|
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
|
||||||
LayerToPrint layer_to_print;
|
ObjectLayerToPrint layer_to_print;
|
||||||
layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
|
layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
|
||||||
layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
|
layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
|
||||||
if (layer_to_print.object_layer && layer_to_print.support_layer) {
|
if (layer_to_print.object_layer && layer_to_print.support_layer) {
|
||||||
@ -575,8 +575,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
|||||||
|
|
||||||
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
|
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
|
||||||
// will be printed for all objects at once.
|
// will be printed for all objects at once.
|
||||||
// Return a list of <print_z, per object LayerToPrint> items.
|
// Return a list of <print_z, per object ObjectLayerToPrint> items.
|
||||||
std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print& print)
|
std::vector<std::pair<coordf_t, GCode::ObjectsLayerToPrint>> GCode::collect_layers_to_print(const Print& print)
|
||||||
{
|
{
|
||||||
struct OrderingItem {
|
struct OrderingItem {
|
||||||
coordf_t print_z;
|
coordf_t print_z;
|
||||||
@ -584,15 +584,15 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||||||
size_t layer_idx;
|
size_t layer_idx;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::vector<LayerToPrint>> per_object(print.objects().size(), std::vector<LayerToPrint>());
|
std::vector<ObjectsLayerToPrint> per_object(print.objects().size(), ObjectsLayerToPrint());
|
||||||
std::vector<OrderingItem> ordering;
|
std::vector<OrderingItem> ordering;
|
||||||
for (size_t i = 0; i < print.objects().size(); ++i) {
|
for (size_t i = 0; i < print.objects().size(); ++i) {
|
||||||
per_object[i] = collect_layers_to_print(*print.objects()[i]);
|
per_object[i] = collect_layers_to_print(*print.objects()[i]);
|
||||||
OrderingItem ordering_item;
|
OrderingItem ordering_item;
|
||||||
ordering_item.object_idx = i;
|
ordering_item.object_idx = i;
|
||||||
ordering.reserve(ordering.size() + per_object[i].size());
|
ordering.reserve(ordering.size() + per_object[i].size());
|
||||||
const LayerToPrint& front = per_object[i].front();
|
const ObjectLayerToPrint &front = per_object[i].front();
|
||||||
for (const LayerToPrint& ltp : per_object[i]) {
|
for (const ObjectLayerToPrint <p : per_object[i]) {
|
||||||
ordering_item.print_z = ltp.print_z();
|
ordering_item.print_z = ltp.print_z();
|
||||||
ordering_item.layer_idx = <p - &front;
|
ordering_item.layer_idx = <p - &front;
|
||||||
ordering.emplace_back(ordering_item);
|
ordering.emplace_back(ordering_item);
|
||||||
@ -601,7 +601,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||||||
|
|
||||||
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
|
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
|
||||||
|
|
||||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
|
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print;
|
||||||
|
|
||||||
// Merge numerically very close Z values.
|
// Merge numerically very close Z values.
|
||||||
for (size_t i = 0; i < ordering.size();) {
|
for (size_t i = 0; i < ordering.size();) {
|
||||||
@ -610,10 +610,10 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||||||
coordf_t zmax = ordering[i].print_z + EPSILON;
|
coordf_t zmax = ordering[i].print_z + EPSILON;
|
||||||
for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
|
for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
|
||||||
// Merge into layers_to_print.
|
// Merge into layers_to_print.
|
||||||
std::pair<coordf_t, std::vector<LayerToPrint>> merged;
|
std::pair<coordf_t, ObjectsLayerToPrint> merged;
|
||||||
// Assign an average print_z to the set of layers with nearly equal print_z.
|
// Assign an average print_z to the set of layers with nearly equal print_z.
|
||||||
merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
|
merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
|
||||||
merged.second.assign(print.objects().size(), LayerToPrint());
|
merged.second.assign(print.objects().size(), ObjectLayerToPrint());
|
||||||
for (; i < j; ++i) {
|
for (; i < j; ++i) {
|
||||||
const OrderingItem& oi = ordering[i];
|
const OrderingItem& oi = ordering[i];
|
||||||
assert(merged.second[oi.object_idx].layer() == nullptr);
|
assert(merged.second[oi.object_idx].layer() == nullptr);
|
||||||
@ -1347,7 +1347,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
} else {
|
} else {
|
||||||
// Sort layers by Z.
|
// Sort layers by Z.
|
||||||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print = collect_layers_to_print(print);
|
||||||
// Prusa Multi-Material wipe tower.
|
// Prusa Multi-Material wipe tower.
|
||||||
if (has_wipe_tower && ! layers_to_print.empty()) {
|
if (has_wipe_tower && ! layers_to_print.empty()) {
|
||||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
|
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
|
||||||
@ -1475,7 +1475,7 @@ void GCode::process_layers(
|
|||||||
const Print &print,
|
const Print &print,
|
||||||
const ToolOrdering &tool_ordering,
|
const ToolOrdering &tool_ordering,
|
||||||
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
||||||
const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> &layers_to_print,
|
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
|
||||||
GCodeOutputStream &output_stream)
|
GCodeOutputStream &output_stream)
|
||||||
{
|
{
|
||||||
// The pipeline is variable: The vase mode filter is optional.
|
// The pipeline is variable: The vase mode filter is optional.
|
||||||
@ -1493,7 +1493,7 @@ void GCode::process_layers(
|
|||||||
return LayerResult::make_nop_layer_result();
|
return LayerResult::make_nop_layer_result();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++];
|
const std::pair<coordf_t, ObjectsLayerToPrint> &layer = layers_to_print[layer_to_print_idx++];
|
||||||
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
|
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
|
||||||
if (m_wipe_tower && layer_tools.has_wipe_tower)
|
if (m_wipe_tower && layer_tools.has_wipe_tower)
|
||||||
m_wipe_tower->next_layer();
|
m_wipe_tower->next_layer();
|
||||||
@ -1559,7 +1559,7 @@ void GCode::process_layers(
|
|||||||
void GCode::process_layers(
|
void GCode::process_layers(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
const ToolOrdering &tool_ordering,
|
const ToolOrdering &tool_ordering,
|
||||||
std::vector<LayerToPrint> layers_to_print,
|
ObjectsLayerToPrint layers_to_print,
|
||||||
const size_t single_object_idx,
|
const size_t single_object_idx,
|
||||||
GCodeOutputStream &output_stream)
|
GCodeOutputStream &output_stream)
|
||||||
{
|
{
|
||||||
@ -1578,7 +1578,7 @@ void GCode::process_layers(
|
|||||||
return LayerResult::make_nop_layer_result();
|
return LayerResult::make_nop_layer_result();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
|
ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
|
||||||
print.throw_if_canceled();
|
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);
|
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);
|
||||||
}
|
}
|
||||||
@ -1829,70 +1829,39 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline GCode::ObjectByExtruder& object_by_extruder(
|
|
||||||
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
|
|
||||||
unsigned int extruder_id,
|
|
||||||
size_t object_idx,
|
|
||||||
size_t num_objects)
|
|
||||||
{
|
|
||||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = by_extruder[extruder_id];
|
|
||||||
if (objects_by_extruder.empty())
|
|
||||||
objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder());
|
|
||||||
return objects_by_extruder[object_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
|
|
||||||
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
|
|
||||||
unsigned int extruder_id,
|
|
||||||
size_t object_idx,
|
|
||||||
size_t num_objects,
|
|
||||||
size_t num_islands)
|
|
||||||
{
|
|
||||||
std::vector<GCode::ObjectByExtruder::Island> &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands;
|
|
||||||
if (islands.empty())
|
|
||||||
islands.assign(num_islands, GCode::ObjectByExtruder::Island());
|
|
||||||
return islands;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
|
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
|
||||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
|
const std::vector<ObjectLayerToPrint> &object_layers,
|
||||||
const std::vector<LayerToPrint> &layers,
|
|
||||||
// Ordering must be defined for normal (non-sequential print).
|
// Ordering must be defined for normal (non-sequential print).
|
||||||
const std::vector<const PrintInstance*> *ordering,
|
const std::vector<const PrintInstance*> *ordering,
|
||||||
// For sequential print, the instance of the object to be printing has to be defined.
|
// For sequential print, the instance of the object to be printing has to be defined.
|
||||||
const size_t single_object_instance_idx)
|
const size_t single_object_instance_idx)
|
||||||
{
|
{
|
||||||
std::vector<InstanceToPrint> out;
|
std::vector<InstanceToPrint> out;
|
||||||
|
|
||||||
if (ordering == nullptr) {
|
if (ordering == nullptr) {
|
||||||
// Sequential print, single object is being printed.
|
// Sequential print, single object is being printed.
|
||||||
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
assert(object_layers.size() == 1);
|
||||||
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
const Layer *layer = object_layers.front().object_layer;
|
||||||
const PrintObject *print_object = layers[layer_id].object();
|
assert(layer != nullptr);
|
||||||
if (print_object)
|
out.emplace_back(0, *layer->object(), single_object_instance_idx);
|
||||||
out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Create mapping from PrintObject* to ObjectByExtruder*.
|
// Create mapping from PrintObject* to ObjectLayerToPrint ID.
|
||||||
std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
|
std::vector<std::pair<const PrintObject*, size_t>> sorted;
|
||||||
sorted.reserve(objects_by_extruder.size());
|
sorted.reserve(object_layers.size());
|
||||||
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
for (const ObjectLayerToPrint &object : object_layers)
|
||||||
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
if (const PrintObject* print_object = object.object(); print_object)
|
||||||
const PrintObject *print_object = layers[layer_id].object();
|
sorted.emplace_back(print_object, &object - object_layers.data());
|
||||||
if (print_object)
|
|
||||||
sorted.emplace_back(print_object, &object_by_extruder);
|
|
||||||
}
|
|
||||||
std::sort(sorted.begin(), sorted.end());
|
std::sort(sorted.begin(), sorted.end());
|
||||||
|
|
||||||
if (! sorted.empty()) {
|
if (! sorted.empty()) {
|
||||||
out.reserve(sorted.size());
|
out.reserve(sorted.size());
|
||||||
for (const PrintInstance *instance : *ordering) {
|
for (const PrintInstance *instance : *ordering) {
|
||||||
const PrintObject &print_object = *instance->print_object;
|
const PrintObject &print_object = *instance->print_object;
|
||||||
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
|
std::pair<const PrintObject*, size_t> key(&print_object, 0);
|
||||||
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
|
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
|
||||||
if (it != sorted.end() && it->first == &print_object)
|
if (it != sorted.end() && it->first == &print_object)
|
||||||
// ObjectByExtruder for this PrintObject was found.
|
// ObjectLayerToPrint for this PrintObject was found.
|
||||||
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
|
out.emplace_back(it->second, print_object, instance - print_object.instances().data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2059,7 +2028,7 @@ namespace Skirt {
|
|||||||
LayerResult GCode::process_layer(
|
LayerResult GCode::process_layer(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||||
const std::vector<LayerToPrint> &layers,
|
const ObjectsLayerToPrint &layers,
|
||||||
const LayerTools &layer_tools,
|
const LayerTools &layer_tools,
|
||||||
const bool last_layer,
|
const bool last_layer,
|
||||||
// Pairs of PrintObject index and its instance index.
|
// Pairs of PrintObject index and its instance index.
|
||||||
@ -2076,7 +2045,7 @@ LayerResult GCode::process_layer(
|
|||||||
const Layer *object_layer = nullptr;
|
const Layer *object_layer = nullptr;
|
||||||
const SupportLayer *support_layer = nullptr;
|
const SupportLayer *support_layer = nullptr;
|
||||||
const SupportLayer *raft_layer = nullptr;
|
const SupportLayer *raft_layer = nullptr;
|
||||||
for (const LayerToPrint &l : layers) {
|
for (const ObjectLayerToPrint &l : layers) {
|
||||||
if (l.object_layer && ! object_layer)
|
if (l.object_layer && ! object_layer)
|
||||||
object_layer = l.object_layer;
|
object_layer = l.object_layer;
|
||||||
if (l.support_layer) {
|
if (l.support_layer) {
|
||||||
@ -2086,7 +2055,7 @@ LayerResult GCode::process_layer(
|
|||||||
raft_layer = support_layer;
|
raft_layer = support_layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
|
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
|
||||||
LayerResult result { {}, layer.id(), false, last_layer, false};
|
LayerResult result { {}, layer.id(), false, last_layer, false};
|
||||||
if (layer_tools.extruders.empty())
|
if (layer_tools.extruders.empty())
|
||||||
// Nothing to extrude.
|
// Nothing to extrude.
|
||||||
@ -2189,157 +2158,6 @@ LayerResult GCode::process_layer(
|
|||||||
Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) :
|
Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) :
|
||||||
Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done);
|
Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done);
|
||||||
|
|
||||||
// Group extrusions by an extruder, then by an object, an island and a region.
|
|
||||||
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
|
|
||||||
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
|
||||||
for (const LayerToPrint &layer_to_print : layers) {
|
|
||||||
if (layer_to_print.support_layer != nullptr) {
|
|
||||||
const SupportLayer &support_layer = *layer_to_print.support_layer;
|
|
||||||
const PrintObject &object = *support_layer.object();
|
|
||||||
if (! support_layer.support_fills.entities.empty()) {
|
|
||||||
ExtrusionRole role = support_layer.support_fills.role();
|
|
||||||
bool has_support = role == erMixed || role == erSupportMaterial;
|
|
||||||
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
|
||||||
// Extruder ID of the support base. -1 if "don't care".
|
|
||||||
unsigned int support_extruder = object.config().support_material_extruder.value - 1;
|
|
||||||
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
|
||||||
bool support_dontcare = object.config().support_material_extruder.value == 0;
|
|
||||||
// Extruder ID of the support interface. -1 if "don't care".
|
|
||||||
unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1;
|
|
||||||
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
|
||||||
bool interface_dontcare = object.config().support_material_interface_extruder.value == 0;
|
|
||||||
if (support_dontcare || interface_dontcare) {
|
|
||||||
// Some support will be printed with "don't care" material, preferably non-soluble.
|
|
||||||
// Is the current extruder assigned a soluble filament?
|
|
||||||
unsigned int dontcare_extruder = first_extruder_id;
|
|
||||||
if (print.config().filament_soluble.get_at(dontcare_extruder)) {
|
|
||||||
// The last extruder printed on the previous layer extrudes soluble filament.
|
|
||||||
// Try to find a non-soluble extruder on the same layer.
|
|
||||||
for (unsigned int extruder_id : layer_tools.extruders)
|
|
||||||
if (! print.config().filament_soluble.get_at(extruder_id)) {
|
|
||||||
dontcare_extruder = extruder_id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (support_dontcare)
|
|
||||||
support_extruder = dontcare_extruder;
|
|
||||||
if (interface_dontcare)
|
|
||||||
interface_extruder = dontcare_extruder;
|
|
||||||
}
|
|
||||||
// Both the support and the support interface are printed with the same extruder, therefore
|
|
||||||
// the interface may be interleaved with the support base.
|
|
||||||
bool single_extruder = ! has_support || support_extruder == interface_extruder;
|
|
||||||
// Assign an extruder to the base.
|
|
||||||
ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size());
|
|
||||||
obj.support = &support_layer.support_fills;
|
|
||||||
obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial;
|
|
||||||
if (! single_extruder && has_interface) {
|
|
||||||
ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size());
|
|
||||||
obj_interface.support = &support_layer.support_fills;
|
|
||||||
obj_interface.support_extrusion_role = erSupportMaterialInterface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (layer_to_print.object_layer != nullptr) {
|
|
||||||
const Layer &layer = *layer_to_print.object_layer;
|
|
||||||
// We now define a strategy for building perimeters and fills. The separation
|
|
||||||
// between regions doesn't matter in terms of printing order, as we follow
|
|
||||||
// another logic instead:
|
|
||||||
// - we group all extrusions by extruder so that we minimize toolchanges
|
|
||||||
// - we start from the last used extruder
|
|
||||||
// - for each extruder, we group extrusions by island
|
|
||||||
// - for each island, we extrude perimeters first, unless user set the infill_first
|
|
||||||
// option
|
|
||||||
// (Still, we have to keep track of regions because we need to apply their config)
|
|
||||||
size_t n_slices = layer.lslices.size();
|
|
||||||
const LayerSlices &layer_surfaces = layer.lslices_ex;
|
|
||||||
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
|
||||||
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
|
||||||
auto point_inside_surface = [&layer, &layer_surfaces](const size_t i, const Point &point) {
|
|
||||||
const BoundingBox &bbox = layer_surfaces[i].bbox;
|
|
||||||
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
|
||||||
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
|
|
||||||
layer.lslices[i].contour.contains(point);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
|
|
||||||
const LayerRegion *layerm = layer.regions()[region_id];
|
|
||||||
if (layerm == nullptr)
|
|
||||||
continue;
|
|
||||||
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
|
|
||||||
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
|
|
||||||
const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id());
|
|
||||||
|
|
||||||
// Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
|
|
||||||
// It is also necessary to save which extrusions are part of MM wiping and which are not.
|
|
||||||
// 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() : layerm->perimeters()) {
|
|
||||||
// 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);
|
|
||||||
if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
|
||||||
int correct_extruder_id = layer_tools.extruder(*extrusions, region);
|
|
||||||
|
|
||||||
// Let's recover vector of extruder overrides:
|
|
||||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
|
||||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
|
||||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
|
||||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
|
||||||
correct_extruder_id = layer_tools.extruders.back();
|
|
||||||
}
|
|
||||||
printing_extruders.clear();
|
|
||||||
if (is_anything_overridden) {
|
|
||||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
|
|
||||||
if (entity_overrides == nullptr) {
|
|
||||||
printing_extruders.emplace_back(correct_extruder_id);
|
|
||||||
} else {
|
|
||||||
printing_extruders.reserve(entity_overrides->size());
|
|
||||||
for (int extruder : *entity_overrides)
|
|
||||||
printing_extruders.emplace_back(extruder >= 0 ?
|
|
||||||
// at least one copy is overridden to use this extruder
|
|
||||||
extruder :
|
|
||||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
|
||||||
static_cast<unsigned int>(- extruder - 1));
|
|
||||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
printing_extruders.emplace_back(correct_extruder_id);
|
|
||||||
|
|
||||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
|
|
||||||
for (unsigned int extruder : printing_extruders)
|
|
||||||
{
|
|
||||||
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
|
|
||||||
by_extruder,
|
|
||||||
extruder,
|
|
||||||
&layer_to_print - layers.data(),
|
|
||||||
layers.size(), n_slices+1);
|
|
||||||
for (size_t i = 0; i <= n_slices; ++ i) {
|
|
||||||
bool last = i == n_slices;
|
|
||||||
// Traverse lslices back to front: lslices are produced by traversing ClipperLib::PolyTree, emitting parent contour before its children.
|
|
||||||
// Therefore traversing layer_surfaces back to front will traverse children contours before their parents.
|
|
||||||
size_t island_idx = last ? n_slices : layer_surfaces.size() - i - 1;
|
|
||||||
if (// extrusions->first_point does not fit inside any slice
|
|
||||||
last ||
|
|
||||||
// extrusions->first_point fits inside ith slice
|
|
||||||
point_inside_surface(island_idx, extrusions->first_point())) {
|
|
||||||
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);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // for regions
|
|
||||||
}
|
|
||||||
} // for objects
|
|
||||||
|
|
||||||
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
||||||
for (unsigned int extruder_id : layer_tools.extruders)
|
for (unsigned int extruder_id : layer_tools.extruders)
|
||||||
{
|
{
|
||||||
@ -2387,91 +2205,30 @@ LayerResult GCode::process_layer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto objects_by_extruder_it = by_extruder.find(extruder_id);
|
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx);
|
||||||
if (objects_by_extruder_it == by_extruder.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
|
||||||
|
|
||||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
//FIXME const_cast
|
||||||
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
||||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
if (is_anything_overridden) {
|
||||||
|
// Extrude wipes.
|
||||||
|
size_t gcode_size_old = gcode.size();
|
||||||
|
for (const InstanceToPrint &instance : instances_to_print)
|
||||||
|
this->process_layer_single_object(
|
||||||
|
gcode, extruder_id, instance,
|
||||||
|
layers[instance.object_layer_to_print_id], layer_tools,
|
||||||
|
is_anything_overridden, true /* print_wipe_extrusions */);
|
||||||
|
if (gcode_size_old < gcode.size())
|
||||||
gcode+="; PURGING FINISHED\n";
|
gcode+="; PURGING FINISHED\n";
|
||||||
|
|
||||||
for (InstanceToPrint &instance_to_print : instances_to_print) {
|
|
||||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
|
||||||
// To control print speed of the 1st object layer printed over raft interface.
|
|
||||||
bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
|
|
||||||
instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
|
|
||||||
m_config.apply(instance_to_print.print_object.config(), true);
|
|
||||||
m_layer = layer_to_print.layer();
|
|
||||||
m_object_layer_over_raft = object_layer_over_raft;
|
|
||||||
if (m_config.avoid_crossing_perimeters)
|
|
||||||
m_avoid_crossing_perimeters.init_layer(*m_layer);
|
|
||||||
if (this->config().gcode_label_objects)
|
|
||||||
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
|
|
||||||
// When starting a new object, use the external motion planner for the first travel move.
|
|
||||||
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
|
||||||
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
|
|
||||||
if (m_last_obj_copy != this_object_copy)
|
|
||||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
|
||||||
m_last_obj_copy = this_object_copy;
|
|
||||||
this->set_origin(unscale(offset));
|
|
||||||
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
|
|
||||||
m_layer = layer_to_print.support_layer;
|
|
||||||
m_object_layer_over_raft = false;
|
|
||||||
gcode += this->extrude_support(
|
|
||||||
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
|
|
||||||
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
|
|
||||||
m_layer = layer_to_print.layer();
|
|
||||||
m_object_layer_over_raft = object_layer_over_raft;
|
|
||||||
}
|
|
||||||
//FIXME order islands?
|
|
||||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
|
||||||
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
|
|
||||||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
|
||||||
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
|
|
||||||
if (print.config().infill_first) {
|
|
||||||
gcode += this->extrude_infill(print, by_region_specific, false);
|
|
||||||
gcode += this->extrude_perimeters(print, by_region_specific);
|
|
||||||
} else {
|
|
||||||
gcode += this->extrude_perimeters(print, by_region_specific);
|
|
||||||
gcode += this->extrude_infill(print,by_region_specific, false);
|
|
||||||
}
|
|
||||||
// ironing
|
|
||||||
gcode += this->extrude_infill(print,by_region_specific, true);
|
|
||||||
}
|
|
||||||
if (this->config().gcode_label_objects)
|
|
||||||
gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Extrude normal extrusions.
|
||||||
|
for (const InstanceToPrint &instance : instances_to_print)
|
||||||
|
this->process_layer_single_object(
|
||||||
|
gcode, extruder_id, instance,
|
||||||
|
layers[instance.object_layer_to_print_id], layer_tools,
|
||||||
|
is_anything_overridden, false /* print_wipe_extrusions */);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Apply spiral vase post-processing if this layer contains suitable geometry
|
|
||||||
// (we must feed all the G-code into the post-processor, including the first
|
|
||||||
// bottom non-spiral layers otherwise it will mess with positions)
|
|
||||||
// we apply spiral vase at this stage because it requires a full layer.
|
|
||||||
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
|
|
||||||
if (m_spiral_vase)
|
|
||||||
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(),
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Apply pressure equalization if enabled;
|
|
||||||
// printf("G-code before filter:\n%s\n", gcode.c_str());
|
|
||||||
if (m_pressure_equalizer)
|
|
||||||
gcode = m_pressure_equalizer->process(gcode.c_str(), false);
|
|
||||||
// printf("G-code after filter:\n%s\n", out.c_str());
|
|
||||||
|
|
||||||
file.write(gcode);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
||||||
log_memory_info();
|
log_memory_info();
|
||||||
|
|
||||||
@ -2480,6 +2237,214 @@ LayerResult GCode::process_layer(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const auto comment_perimeter = "perimeter"sv;
|
||||||
|
// Comparing string_view pointer & length for speed.
|
||||||
|
static inline bool comment_is_perimeter(const std::string_view comment) {
|
||||||
|
return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCode::process_layer_single_object(
|
||||||
|
// output
|
||||||
|
std::string &gcode,
|
||||||
|
// Index of the extruder currently active.
|
||||||
|
const unsigned int extruder_id,
|
||||||
|
// What object and instance is going to be printed.
|
||||||
|
const InstanceToPrint &print_instance,
|
||||||
|
// and the object & support layer of the above.
|
||||||
|
const ObjectLayerToPrint &layer_to_print,
|
||||||
|
// Container for extruder overrides (when wiping into object or infill).
|
||||||
|
const LayerTools &layer_tools,
|
||||||
|
// Is any extrusion possibly marked as wiping extrusion?
|
||||||
|
const bool is_anything_overridden,
|
||||||
|
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
|
||||||
|
const bool print_wipe_extrusions)
|
||||||
|
{
|
||||||
|
//FIXME what the heck ID is this? Layer ID or Object ID? More likely an Object ID.
|
||||||
|
uint32_t layer_id = 0;
|
||||||
|
bool first = true;
|
||||||
|
// Delay layer initialization as many layers may not print with all extruders.
|
||||||
|
auto init_layer_delayed = [this, &print_instance, &layer_to_print, layer_id, &first, &gcode]() {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
const PrintObject &print_object = print_instance.print_object;
|
||||||
|
const Print &print = *print_object.print();
|
||||||
|
m_config.apply(print_object.config(), true);
|
||||||
|
m_layer = layer_to_print.layer();
|
||||||
|
if (print.config().avoid_crossing_perimeters)
|
||||||
|
m_avoid_crossing_perimeters.init_layer(*m_layer);
|
||||||
|
// When starting a new object, use the external motion planner for the first travel move.
|
||||||
|
const Point &offset = print_object.instances()[print_instance.instance_id].shift;
|
||||||
|
std::pair<const PrintObject*, Point> this_object_copy(&print_object, offset);
|
||||||
|
if (m_last_obj_copy != this_object_copy)
|
||||||
|
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
|
m_last_obj_copy = this_object_copy;
|
||||||
|
this->set_origin(unscale(offset));
|
||||||
|
if (this->config().gcode_label_objects)
|
||||||
|
gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PrintObject &print_object = print_instance.print_object;
|
||||||
|
const Print &print = *print_object.print();
|
||||||
|
|
||||||
|
if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr)
|
||||||
|
if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) {
|
||||||
|
ExtrusionRole role = support_layer.support_fills.role();
|
||||||
|
bool has_support = role == erMixed || role == erSupportMaterial;
|
||||||
|
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
||||||
|
// Extruder ID of the support base. -1 if "don't care".
|
||||||
|
unsigned int support_extruder = print_object.config().support_material_extruder.value - 1;
|
||||||
|
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
||||||
|
bool support_dontcare = print_object.config().support_material_extruder.value == 0;
|
||||||
|
// Extruder ID of the support interface. -1 if "don't care".
|
||||||
|
unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1;
|
||||||
|
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
||||||
|
bool interface_dontcare = print_object.config().support_material_interface_extruder.value == 0;
|
||||||
|
if (support_dontcare || interface_dontcare) {
|
||||||
|
// Some support will be printed with "don't care" material, preferably non-soluble.
|
||||||
|
// Is the current extruder assigned a soluble filament?
|
||||||
|
unsigned int dontcare_extruder = layer_tools.extruders.front();
|
||||||
|
if (print.config().filament_soluble.get_at(dontcare_extruder)) {
|
||||||
|
// The last extruder printed on the previous layer extrudes soluble filament.
|
||||||
|
// Try to find a non-soluble extruder on the same layer.
|
||||||
|
for (unsigned int extruder_id : layer_tools.extruders)
|
||||||
|
if (! print.config().filament_soluble.get_at(extruder_id)) {
|
||||||
|
dontcare_extruder = extruder_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (support_dontcare)
|
||||||
|
support_extruder = dontcare_extruder;
|
||||||
|
if (interface_dontcare)
|
||||||
|
interface_extruder = dontcare_extruder;
|
||||||
|
}
|
||||||
|
bool extrude_support = has_support && support_extruder == extruder_id;
|
||||||
|
bool extrude_interface = interface_extruder && interface_extruder == extruder_id;
|
||||||
|
if (extrude_support || extrude_interface) {
|
||||||
|
init_layer_delayed();
|
||||||
|
m_layer = layer_to_print.support_layer;
|
||||||
|
m_object_layer_over_raft = false;
|
||||||
|
gcode += this->extrude_support(
|
||||||
|
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
|
||||||
|
support_layer.support_fills.chained_path_from(m_last_pos, has_support ? (has_interface ? erMixed : erSupportMaterial) : erSupportMaterialInterface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_layer = layer_to_print.layer();
|
||||||
|
// To control print speed of the 1st object layer printed over raft interface.
|
||||||
|
m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
|
||||||
|
print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
|
||||||
|
|
||||||
|
// Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions
|
||||||
|
// (wipe extrusions are printed before regular extrusions).
|
||||||
|
auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool {
|
||||||
|
assert(eec != nullptr);
|
||||||
|
if (eec->entities.empty())
|
||||||
|
// This shouldn't happen. FIXME why? but first_point() would fail.
|
||||||
|
return false;
|
||||||
|
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
||||||
|
int correct_extruder_id = layer_tools.extruder(*eec, region);
|
||||||
|
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||||
|
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||||
|
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||||
|
correct_extruder_id = layer_tools.extruders.back();
|
||||||
|
}
|
||||||
|
//FIXME const_cast!
|
||||||
|
int extruder_override_id = is_anything_overridden ? const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_override(eec, instance_id) : -1;
|
||||||
|
return print_wipe_extrusions ?
|
||||||
|
extruder_override_id == int(extruder_id) :
|
||||||
|
extruder_override_id < 0 && extruder_id == correct_extruder_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtrusionEntitiesPtr temp_fill_extrusions;
|
||||||
|
if (const Layer *layer = layer_to_print.object_layer; layer)
|
||||||
|
for (const LayerSlice &lslice : layer->lslices_ex) {
|
||||||
|
auto extrude_infill_range = [&](const LayerRegion &layerm, const ExtrusionEntityCollection &fills, const ExtrusionRange fill_range, bool ironing) {
|
||||||
|
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
|
||||||
|
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
|
||||||
|
const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id());
|
||||||
|
temp_fill_extrusions.clear();
|
||||||
|
for (uint32_t fill_id : fill_range)
|
||||||
|
if (auto *eec = static_cast<ExtrusionEntityCollection*>(layerm.fills().entities[fill_id]);
|
||||||
|
(eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) {
|
||||||
|
if (eec->can_reverse())
|
||||||
|
// Flatten the infill collection for better path planning.
|
||||||
|
for (auto *ee : eec->entities)
|
||||||
|
temp_fill_extrusions.emplace_back(ee);
|
||||||
|
else
|
||||||
|
temp_fill_extrusions.emplace_back(eec);
|
||||||
|
}
|
||||||
|
if (! temp_fill_extrusions.empty()) {
|
||||||
|
init_layer_delayed();
|
||||||
|
m_config.apply(region.config());
|
||||||
|
//FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview?
|
||||||
|
// Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues?
|
||||||
|
chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos);
|
||||||
|
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
|
||||||
|
for (const ExtrusionEntity *fill : temp_fill_extrusions)
|
||||||
|
if (auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill); eec) {
|
||||||
|
for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
|
||||||
|
gcode += this->extrude_entity(*ee, extrusion_name);
|
||||||
|
} else
|
||||||
|
gcode += this->extrude_entity(*fill, extrusion_name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//FIXME order islands?
|
||||||
|
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||||
|
for (const LayerIsland &island : lslice.islands) {
|
||||||
|
auto process_perimeters = [&]() {
|
||||||
|
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
|
||||||
|
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
|
||||||
|
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
|
||||||
|
const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id());
|
||||||
|
bool first = true;
|
||||||
|
for (uint32_t perimeter_id : island.perimeters)
|
||||||
|
if (const auto *eec = static_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]);
|
||||||
|
shall_print_this_extrusion_collection(eec, region)) {
|
||||||
|
assert(! eec->can_reverse());
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
init_layer_delayed();
|
||||||
|
m_config.apply(region.config());
|
||||||
|
}
|
||||||
|
for (const ExtrusionEntity *ee : *eec)
|
||||||
|
gcode += this->extrude_entity(*ee, comment_perimeter, -1.);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto process_infill = [&]() {
|
||||||
|
for (LayerExtrusionRange fill_range : island.fills) {
|
||||||
|
const LayerRegion &layerm = *layer->get_region(fill_range.region());
|
||||||
|
extrude_infill_range(layerm, layerm.fills(), fill_range, false /* normal extrusions, not ironing */);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
|
||||||
|
extrude_infill_range(layerm, layerm.thin_fills(), island.thin_fills, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (print.config().infill_first) {
|
||||||
|
process_infill();
|
||||||
|
process_perimeters();
|
||||||
|
} else {
|
||||||
|
process_perimeters();
|
||||||
|
process_infill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ironing
|
||||||
|
//FIXME move ironing into the loop above over LayerIslands?
|
||||||
|
// First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions.
|
||||||
|
// Ironing in a second phase is safer, but it may be less efficient.
|
||||||
|
for (const LayerIsland &island : lslice.islands) {
|
||||||
|
for (LayerExtrusionRange fill_range : island.fills) {
|
||||||
|
const LayerRegion &layerm = *layer->get_region(fill_range.region());
|
||||||
|
extrude_infill_range(layerm, layerm.fills(), fill_range, true /* ironing, not normal extrusions */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! first && this->config().gcode_label_objects)
|
||||||
|
gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
void GCode::apply_print_config(const PrintConfig &print_config)
|
void GCode::apply_print_config(const PrintConfig &print_config)
|
||||||
{
|
{
|
||||||
m_writer.apply_print_config(print_config);
|
m_writer.apply_print_config(print_config);
|
||||||
@ -2569,12 +2534,6 @@ std::string GCode::change_layer(coordf_t print_z)
|
|||||||
return gcode;
|
return gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const auto comment_perimeter = "perimeter"sv;
|
|
||||||
// Comparing string_view pointer & length for speed.
|
|
||||||
static inline bool comment_is_perimeter(const std::string_view comment) {
|
|
||||||
return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed)
|
std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed)
|
||||||
{
|
{
|
||||||
// get a copy; don't modify the orientation of the original loop object otherwise
|
// get a copy; don't modify the orientation of the original loop object otherwise
|
||||||
@ -2741,49 +2700,6 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string_view description
|
|||||||
return gcode;
|
return gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extrude perimeters: Decide where to put seams (hide or align seams).
|
|
||||||
std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
|
|
||||||
{
|
|
||||||
std::string gcode;
|
|
||||||
for (const ObjectByExtruder::Island::Region ®ion : by_region)
|
|
||||||
if (! region.perimeters.empty()) {
|
|
||||||
m_config.apply(print.get_print_region(®ion - &by_region.front()).config());
|
|
||||||
|
|
||||||
for (const ExtrusionEntity* ee : region.perimeters)
|
|
||||||
gcode += this->extrude_entity(*ee, comment_perimeter, -1.);
|
|
||||||
}
|
|
||||||
return gcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
|
|
||||||
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing)
|
|
||||||
{
|
|
||||||
std::string gcode;
|
|
||||||
ExtrusionEntitiesPtr extrusions;
|
|
||||||
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
|
|
||||||
for (const ObjectByExtruder::Island::Region ®ion : by_region)
|
|
||||||
if (! region.infills.empty()) {
|
|
||||||
extrusions.clear();
|
|
||||||
extrusions.reserve(region.infills.size());
|
|
||||||
for (ExtrusionEntity *ee : region.infills)
|
|
||||||
if ((ee->role() == erIroning) == ironing)
|
|
||||||
extrusions.emplace_back(ee);
|
|
||||||
if (! extrusions.empty()) {
|
|
||||||
m_config.apply(print.get_print_region(®ion - &by_region.front()).config());
|
|
||||||
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
|
|
||||||
for (const ExtrusionEntity *fill : extrusions) {
|
|
||||||
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
|
|
||||||
if (eec) {
|
|
||||||
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
|
|
||||||
gcode += this->extrude_entity(*ee, extrusion_name);
|
|
||||||
} else
|
|
||||||
gcode += this->extrude_entity(*fill, extrusion_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills)
|
std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills)
|
||||||
{
|
{
|
||||||
static constexpr const auto support_label = "support material"sv;
|
static constexpr const auto support_label = "support material"sv;
|
||||||
@ -3302,104 +3218,4 @@ Point GCode::gcode_to_point(const Vec2d &point) const
|
|||||||
scale_(point(1) - m_origin(1) + extruder_offset(1)));
|
scale_(point(1) - m_origin(1) + extruder_offset(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
|
|
||||||
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
|
|
||||||
// Fills in by_region_per_copy_cache and returns its reference.
|
|
||||||
const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const
|
|
||||||
{
|
|
||||||
bool has_overrides = false;
|
|
||||||
for (const auto& reg : by_region)
|
|
||||||
if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
|
|
||||||
has_overrides = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data is cleared, but the memory is not.
|
|
||||||
by_region_per_copy_cache.clear();
|
|
||||||
|
|
||||||
if (! has_overrides)
|
|
||||||
// Simple case. No need to copy the regions.
|
|
||||||
return wiping_entities ? by_region_per_copy_cache : this->by_region;
|
|
||||||
|
|
||||||
// Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
|
|
||||||
// Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
|
|
||||||
// Filter out the extrusions based on the infill_overrides / perimeter_overrides:
|
|
||||||
|
|
||||||
for (const auto& reg : by_region) {
|
|
||||||
by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island
|
|
||||||
|
|
||||||
// Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
|
|
||||||
// References are used so that we don't have to repeat the same code
|
|
||||||
for (int iter = 0; iter < 2; ++iter) {
|
|
||||||
const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters);
|
|
||||||
ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
|
|
||||||
const std::vector<const WipingExtrusions::ExtruderPerCopy*>& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides);
|
|
||||||
|
|
||||||
// Now the most important thing - which extrusion should we print.
|
|
||||||
// See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
|
|
||||||
if (wiping_entities) {
|
|
||||||
// Apply overrides for this region.
|
|
||||||
for (unsigned int i = 0; i < overrides.size(); ++ i) {
|
|
||||||
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
|
|
||||||
// This copy (aka object instance) should be printed with this extruder, which overrides the default one.
|
|
||||||
if (this_override != nullptr && (*this_override)[copy] == int(extruder))
|
|
||||||
target_eec.emplace_back(entities[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Apply normal extrusions (non-overrides) for this region.
|
|
||||||
unsigned int i = 0;
|
|
||||||
for (; i < overrides.size(); ++ i) {
|
|
||||||
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
|
|
||||||
// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
|
|
||||||
if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
|
|
||||||
target_eec.emplace_back(entities[i]);
|
|
||||||
}
|
|
||||||
for (; i < entities.size(); ++ i)
|
|
||||||
target_eec.emplace_back(entities[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return by_region_per_copy_cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter)
|
|
||||||
// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy.
|
|
||||||
void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder)
|
|
||||||
{
|
|
||||||
// We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
|
|
||||||
ExtrusionEntitiesPtr* perimeters_or_infills;
|
|
||||||
std::vector<const WipingExtrusions::ExtruderPerCopy*>* perimeters_or_infills_overrides;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case PERIMETERS:
|
|
||||||
perimeters_or_infills = &perimeters;
|
|
||||||
perimeters_or_infills_overrides = &perimeters_overrides;
|
|
||||||
break;
|
|
||||||
case INFILL:
|
|
||||||
perimeters_or_infills = &infills;
|
|
||||||
perimeters_or_infills_overrides = &infills_overrides;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw Slic3r::InvalidArgument("Unknown parameter!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// First we append the entities, there are eec->entities.size() of them:
|
|
||||||
size_t old_size = perimeters_or_infills->size();
|
|
||||||
size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1);
|
|
||||||
perimeters_or_infills->reserve(new_size);
|
|
||||||
if (eec->can_reverse()) {
|
|
||||||
for (auto* ee : eec->entities)
|
|
||||||
perimeters_or_infills->emplace_back(ee);
|
|
||||||
} else
|
|
||||||
perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
|
|
||||||
|
|
||||||
if (copies_extruder != nullptr) {
|
|
||||||
// Don't reallocate overrides if not needed.
|
|
||||||
// Missing overrides are implicitely considered non-overridden.
|
|
||||||
perimeters_or_infills_overrides->reserve(new_size);
|
|
||||||
perimeters_or_infills_overrides->resize(old_size, nullptr);
|
|
||||||
perimeters_or_infills_overrides->resize(new_size, copies_extruder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -188,15 +188,16 @@ public:
|
|||||||
|
|
||||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||||
// public, so that it could be accessed by free helper functions from GCode.cpp
|
// public, so that it could be accessed by free helper functions from GCode.cpp
|
||||||
struct LayerToPrint
|
struct ObjectLayerToPrint
|
||||||
{
|
{
|
||||||
LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
|
ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
|
||||||
const Layer* object_layer;
|
const Layer* object_layer;
|
||||||
const SupportLayer* support_layer;
|
const SupportLayer* support_layer;
|
||||||
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
|
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
|
||||||
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
|
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
|
||||||
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
|
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
|
||||||
};
|
};
|
||||||
|
using ObjectsLayerToPrint = std::vector<ObjectLayerToPrint>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class GCodeOutputStream {
|
class GCodeOutputStream {
|
||||||
@ -239,13 +240,13 @@ private:
|
|||||||
};
|
};
|
||||||
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
|
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
|
||||||
|
|
||||||
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
|
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
|
||||||
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
|
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
|
||||||
|
|
||||||
LayerResult process_layer(
|
LayerResult process_layer(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||||
const std::vector<LayerToPrint> &layers,
|
const ObjectsLayerToPrint &layers,
|
||||||
const LayerTools &layer_tools,
|
const LayerTools &layer_tools,
|
||||||
const bool last_layer,
|
const bool last_layer,
|
||||||
// Pairs of PrintObject index and its instance index.
|
// Pairs of PrintObject index and its instance index.
|
||||||
@ -257,18 +258,18 @@ private:
|
|||||||
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
||||||
// and export G-code into file.
|
// and export G-code into file.
|
||||||
void process_layers(
|
void process_layers(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
const ToolOrdering &tool_ordering,
|
const ToolOrdering &tool_ordering,
|
||||||
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
||||||
const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> &layers_to_print,
|
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
|
||||||
GCodeOutputStream &output_stream);
|
GCodeOutputStream &output_stream);
|
||||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
// 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
|
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
||||||
// and export G-code into file.
|
// and export G-code into file.
|
||||||
void process_layers(
|
void process_layers(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
const ToolOrdering &tool_ordering,
|
const ToolOrdering &tool_ordering,
|
||||||
std::vector<LayerToPrint> layers_to_print,
|
ObjectsLayerToPrint layers_to_print,
|
||||||
const size_t single_object_idx,
|
const size_t single_object_idx,
|
||||||
GCodeOutputStream &output_stream);
|
GCodeOutputStream &output_stream);
|
||||||
|
|
||||||
@ -282,71 +283,43 @@ private:
|
|||||||
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
|
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
|
||||||
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.);
|
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.);
|
||||||
|
|
||||||
// Extruding multiple objects with soluble / non-soluble / combined supports
|
struct InstanceToPrint
|
||||||
// on a multi-material printer, trying to minimize tool switches.
|
|
||||||
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
|
|
||||||
struct ObjectByExtruder
|
|
||||||
{
|
{
|
||||||
ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {}
|
InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) :
|
||||||
const ExtrusionEntityCollection *support;
|
object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {}
|
||||||
// erSupportMaterial / erSupportMaterialInterface or erMixed.
|
|
||||||
ExtrusionRole support_extrusion_role;
|
|
||||||
|
|
||||||
struct Island
|
// Index into std::vector<ObjectLayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
|
||||||
{
|
const size_t object_layer_to_print_id;
|
||||||
struct Region {
|
const PrintObject &print_object;
|
||||||
// Non-owned references to LayerRegion::perimeters::entities
|
// Instance idx of the copy of a print object.
|
||||||
// std::vector<const ExtrusionEntity*> would be better here, but there is no way in C++ to convert from std::vector<T*> std::vector<const T*> without copying.
|
const size_t instance_id;
|
||||||
ExtrusionEntitiesPtr perimeters;
|
|
||||||
// Non-owned references to LayerRegion::fills::entities
|
|
||||||
ExtrusionEntitiesPtr infills;
|
|
||||||
|
|
||||||
std::vector<const WipingExtrusions::ExtruderPerCopy*> infills_overrides;
|
|
||||||
std::vector<const WipingExtrusions::ExtruderPerCopy*> perimeters_overrides;
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
PERIMETERS,
|
|
||||||
INFILL,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping
|
|
||||||
void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
std::vector<Region> by_region; // all extrusions for this island, grouped by regions
|
|
||||||
|
|
||||||
// Fills in by_region_per_copy_cache and returns its reference.
|
|
||||||
const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const;
|
|
||||||
};
|
|
||||||
std::vector<Island> islands;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InstanceToPrint
|
std::vector<InstanceToPrint> sort_print_object_instances(
|
||||||
{
|
// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
|
||||||
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
|
const std::vector<ObjectLayerToPrint> &layers,
|
||||||
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
|
// Ordering must be defined for normal (non-sequential print).
|
||||||
|
const std::vector<const PrintInstance*> *ordering,
|
||||||
|
// For sequential print, the instance of the object to be printing has to be defined.
|
||||||
|
const size_t single_object_instance_idx);
|
||||||
|
|
||||||
// Repository
|
// This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions.
|
||||||
ObjectByExtruder &object_by_extruder;
|
void process_layer_single_object(
|
||||||
// Index into std::vector<LayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
|
// output
|
||||||
const size_t layer_id;
|
std::string &gcode,
|
||||||
const PrintObject &print_object;
|
// Index of the extruder currently active.
|
||||||
// Instance idx of the copy of a print object.
|
const unsigned int extruder_id,
|
||||||
const size_t instance_id;
|
// What object and instance is going to be printed.
|
||||||
};
|
const InstanceToPrint &print_instance,
|
||||||
|
// and the object & support layer of the above.
|
||||||
|
const ObjectLayerToPrint &layer_to_print,
|
||||||
|
// Container for extruder overrides (when wiping into object or infill).
|
||||||
|
const LayerTools &layer_tools,
|
||||||
|
// Is any extrusion possibly marked as wiping extrusion?
|
||||||
|
const bool is_anything_overridden,
|
||||||
|
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
|
||||||
|
const bool print_wipe_extrusions);
|
||||||
|
|
||||||
std::vector<InstanceToPrint> sort_print_object_instances(
|
|
||||||
std::vector<ObjectByExtruder> &objects_by_extruder,
|
|
||||||
// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
|
|
||||||
const std::vector<LayerToPrint> &layers,
|
|
||||||
// Ordering must be defined for normal (non-sequential print).
|
|
||||||
const std::vector<const PrintInstance*> *ordering,
|
|
||||||
// For sequential print, the instance of the object to be printing has to be defined.
|
|
||||||
const size_t single_object_instance_idx);
|
|
||||||
|
|
||||||
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
|
|
||||||
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing);
|
|
||||||
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
|
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
|
||||||
|
|
||||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||||
@ -438,18 +411,6 @@ private:
|
|||||||
// To control print speed of 1st object layer over raft interface.
|
// To control print speed of 1st object layer over raft interface.
|
||||||
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
|
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
|
||||||
|
|
||||||
friend ObjectByExtruder& object_by_extruder(
|
|
||||||
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
|
|
||||||
unsigned int extruder_id,
|
|
||||||
size_t object_idx,
|
|
||||||
size_t num_objects);
|
|
||||||
friend std::vector<ObjectByExtruder::Island>& object_islands_by_extruder(
|
|
||||||
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
|
|
||||||
unsigned int extruder_id,
|
|
||||||
size_t object_idx,
|
|
||||||
size_t num_objects,
|
|
||||||
size_t num_islands);
|
|
||||||
|
|
||||||
friend class Wipe;
|
friend class Wipe;
|
||||||
friend class WipeTowerIntegration;
|
friend class WipeTowerIntegration;
|
||||||
friend class PressureEqualizer;
|
friend class PressureEqualizer;
|
||||||
|
@ -103,7 +103,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
|
|||||||
}
|
}
|
||||||
double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
|
double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
|
||||||
|
|
||||||
// Collect extruders reuqired to print the layers.
|
// Collect extruders required to print the layers.
|
||||||
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
|
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
|
||||||
|
|
||||||
// Reorder the extruders to minimize tool switches.
|
// Reorder the extruders to minimize tool switches.
|
||||||
@ -598,14 +598,15 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
|
|||||||
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
|
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
|
||||||
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
|
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
|
||||||
{
|
{
|
||||||
something_overridden = true;
|
m_something_overridden = true;
|
||||||
|
|
||||||
auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
|
auto entity_map_it = (m_entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
|
||||||
ExtruderPerCopy& copies_vector = entity_map_it->second;
|
ExtruderPerCopy& copies_vector = entity_map_it->second;
|
||||||
copies_vector.resize(num_of_copies, -1);
|
copies_vector.resize(num_of_copies, -1);
|
||||||
|
|
||||||
|
assert(copies_vector[copy_id] == -1);
|
||||||
if (copies_vector[copy_id] != -1)
|
if (copies_vector[copy_id] != -1)
|
||||||
std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
|
BOOST_LOG_TRIVIAL(error) << "ERROR: Entity extruder overriden multiple times!!!";
|
||||||
|
|
||||||
copies_vector[copy_id] = extruder;
|
copies_vector[copy_id] = extruder;
|
||||||
}
|
}
|
||||||
@ -649,13 +650,19 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con
|
|||||||
|
|
||||||
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
|
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
|
||||||
// and returns volume that is left to be wiped on the wipe tower.
|
// and returns volume that is left to be wiped on the wipe tower.
|
||||||
|
// Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable).
|
||||||
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
|
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
|
||||||
{
|
{
|
||||||
const LayerTools& lt = *m_layer_tools;
|
const LayerTools& lt = *m_layer_tools;
|
||||||
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
|
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
|
||||||
|
|
||||||
if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
|
if (! m_something_overridable || volume_to_wipe <= 0. ||
|
||||||
return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it
|
// Don't wipe a soluble filament into another object.
|
||||||
|
print.config().filament_soluble.get_at(old_extruder) ||
|
||||||
|
// Don't prime a soluble filament into another object.
|
||||||
|
print.config().filament_soluble.get_at(new_extruder))
|
||||||
|
// Soluble filament cannot be wiped in a random infill, neither the filament after it
|
||||||
|
return std::max(0.f, volume_to_wipe);
|
||||||
|
|
||||||
// we will sort objects so that dedicated for wiping are at the beginning:
|
// we will sort objects so that dedicated for wiping are at the beginning:
|
||||||
ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end());
|
ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end());
|
||||||
@ -742,7 +749,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||||||
// them again and make sure we override it.
|
// them again and make sure we override it.
|
||||||
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
||||||
{
|
{
|
||||||
if (! this->something_overridable)
|
if (! m_something_overridable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const LayerTools& lt = *m_layer_tools;
|
const LayerTools& lt = *m_layer_tools;
|
||||||
@ -775,8 +782,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||||||
// Either way, we will now force-override it with something suitable:
|
// Either way, we will now force-override it with something suitable:
|
||||||
if (print.config().infill_first
|
if (print.config().infill_first
|
||||||
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||||
|| lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
|| lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder) // !infill_first, but perimeter is already printed when last extruder prints
|
||||||
|| ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME)
|
|| ! lt.has_extruder(lt.infill_extruder(region))) // we have to force override - this could violate infill_first (FIXME)
|
||||||
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||||
else {
|
else {
|
||||||
// In this case we can (and should) leave it to be printed normally.
|
// In this case we can (and should) leave it to be printed normally.
|
||||||
@ -795,24 +802,4 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
|
|
||||||
// If this extrusion does not have any override, nullptr is returned.
|
|
||||||
// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
|
|
||||||
// so -1 was used as "print as usual").
|
|
||||||
// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
|
|
||||||
// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
|
|
||||||
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
|
|
||||||
{
|
|
||||||
ExtruderPerCopy *overrides = nullptr;
|
|
||||||
auto entity_map_it = entity_map.find(entity);
|
|
||||||
if (entity_map_it != entity_map.end()) {
|
|
||||||
overrides = &entity_map_it->second;
|
|
||||||
overrides->resize(num_of_copies, -1);
|
|
||||||
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
|
|
||||||
std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1);
|
|
||||||
}
|
|
||||||
return overrides;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -24,14 +24,19 @@ class WipingExtrusions
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
|
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
|
||||||
return something_overridden;
|
return m_something_overridden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
|
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
|
||||||
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
|
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
|
||||||
|
|
||||||
// This is called from GCode::process_layer - see implementation for further comments:
|
// This is called from GCode::process_layer_single_object()
|
||||||
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
|
// Returns positive number if the extruder is overridden.
|
||||||
|
// Returns -1 if not.
|
||||||
|
int get_extruder_override(const ExtrusionEntity* entity, uint32_t instance_id) const {
|
||||||
|
auto entity_map_it = m_entity_map.find(entity);
|
||||||
|
return entity_map_it == m_entity_map.end() ? -1 : entity_map_it->second[instance_id];
|
||||||
|
}
|
||||||
|
|
||||||
// This function goes through all infill entities, decides which ones will be used for wiping and
|
// This function goes through all infill entities, decides which ones will be used for wiping and
|
||||||
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
|
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
|
||||||
@ -42,7 +47,7 @@ public:
|
|||||||
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
||||||
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
|
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
|
||||||
bool out = this->is_overriddable(ee, print_config, object, region);
|
bool out = this->is_overriddable(ee, print_config, object, region);
|
||||||
this->something_overridable |= out;
|
m_something_overridable |= out;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,13 +62,13 @@ private:
|
|||||||
|
|
||||||
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
||||||
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
|
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
|
||||||
auto it = entity_map.find(entity);
|
auto it = m_entity_map.find(entity);
|
||||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
return it == m_entity_map.end() ? false : it->second[copy_id] != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
std::map<const ExtrusionEntity*, ExtruderPerCopy> m_entity_map; // to keep track of who prints what
|
||||||
bool something_overridable = false;
|
bool m_something_overridable = false;
|
||||||
bool something_overridden = false;
|
bool m_something_overridden = false;
|
||||||
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
|
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -507,16 +507,43 @@ void Layer::sort_perimeters_into_islands(
|
|||||||
for (const ExPolygon &expolygon : fill_expolygons)
|
for (const ExPolygon &expolygon : fill_expolygons)
|
||||||
fill_expolygons_bboxes.emplace_back(get_extents(expolygon));
|
fill_expolygons_bboxes.emplace_back(get_extents(expolygon));
|
||||||
|
|
||||||
|
|
||||||
|
// Take one sample point for each source slice, to be used to sort source slices into layer slices.
|
||||||
|
// source slice index + its sample.
|
||||||
|
std::vector<std::pair<uint32_t, Point>> perimeter_slices_queue;
|
||||||
|
perimeter_slices_queue.reserve(slices.size());
|
||||||
|
for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) {
|
||||||
|
const std::pair<ExtrusionRange, ExtrusionRange> &extrusions = perimeter_and_gapfill_ranges[islice];
|
||||||
|
Point sample;
|
||||||
|
bool sample_set = false;
|
||||||
|
if (! extrusions.first.empty()) {
|
||||||
|
sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point();
|
||||||
|
sample_set = true;
|
||||||
|
} else if (! extrusions.second.empty()) {
|
||||||
|
sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point();
|
||||||
|
sample_set = true;
|
||||||
|
} else {
|
||||||
|
for (uint32_t iexpoly : fill_expolygons_ranges[islice])
|
||||||
|
if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) {
|
||||||
|
sample = expoly.contour.points.front();
|
||||||
|
sample_set = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There may be a valid empty island.
|
||||||
|
// assert(sample_set);
|
||||||
|
if (sample_set)
|
||||||
|
perimeter_slices_queue.emplace_back(islice, sample);
|
||||||
|
}
|
||||||
|
|
||||||
// Map of source fill_expolygon into region and fill_expolygon of that region.
|
// Map of source fill_expolygon into region and fill_expolygon of that region.
|
||||||
// -1: not set
|
// -1: not set
|
||||||
std::vector<std::pair<int, int>> map_expolygon_to_region_and_fill;
|
std::vector<std::pair<int, int>> map_expolygon_to_region_and_fill;
|
||||||
|
const bool has_multiple_regions = layer_region_ids.size() > 1;
|
||||||
|
assert(has_multiple_regions || layer_region_ids.size() == 1);
|
||||||
// assign fill_surfaces to each layer
|
// assign fill_surfaces to each layer
|
||||||
if (! fill_expolygons.empty()) {
|
if (! fill_expolygons.empty()) {
|
||||||
if (layer_region_ids.size() == 1) {
|
if (has_multiple_regions) {
|
||||||
this_layer_region.m_fill_expolygons = std::move(fill_expolygons);
|
|
||||||
this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes);
|
|
||||||
} else {
|
|
||||||
// Sort the bounding boxes lexicographically.
|
// Sort the bounding boxes lexicographically.
|
||||||
std::vector<uint32_t> fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size());
|
std::vector<uint32_t> fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size());
|
||||||
std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0);
|
std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0);
|
||||||
@ -550,106 +577,133 @@ void Layer::sort_perimeters_into_islands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this_layer_region.m_fill_expolygons = std::move(fill_expolygons);
|
||||||
|
this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
|
||||||
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
|
||||||
auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) {
|
|
||||||
const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox;
|
|
||||||
return point.x() >= bbox.min.x() && point.x() < bbox.max.x() &&
|
|
||||||
point.y() >= bbox.min.y() && point.y() < bbox.max.y() &&
|
|
||||||
this->lslices[lslice_idx].contour.contains(point);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Take one sample point for each source slice, to be used to sort source slices into layer slices.
|
|
||||||
// source slice index + its sample.
|
|
||||||
std::vector<std::pair<uint32_t, Point>> perimeter_slices_queue;
|
|
||||||
perimeter_slices_queue.reserve(slices.size());
|
|
||||||
for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) {
|
|
||||||
const std::pair<ExtrusionRange, ExtrusionRange> &extrusions = perimeter_and_gapfill_ranges[islice];
|
|
||||||
Point sample;
|
|
||||||
bool sample_set = false;
|
|
||||||
if (! extrusions.first.empty()) {
|
|
||||||
sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point();
|
|
||||||
sample_set = true;
|
|
||||||
} else if (! extrusions.second.empty()) {
|
|
||||||
sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point();
|
|
||||||
sample_set = true;
|
|
||||||
} else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) {
|
|
||||||
for (uint32_t iexpoly : fill_expolygon_range)
|
|
||||||
if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) {
|
|
||||||
sample = expoly.contour.points.front();
|
|
||||||
sample_set = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sample_set)
|
|
||||||
perimeter_slices_queue.emplace_back(islice, sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands.
|
// Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands.
|
||||||
std::vector<uint32_t> region_fill_sorted_last;
|
std::vector<uint32_t> region_fill_sorted_last;
|
||||||
for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) {
|
auto insert_into_island = [
|
||||||
|
// Region where the perimeters, gap fills and fill expolygons are stored.
|
||||||
|
region_id,
|
||||||
|
// Whether there are infills with different regions generated for this LayerSlice.
|
||||||
|
has_multiple_regions,
|
||||||
|
// Perimeters and gap fills to be sorted into islands.
|
||||||
|
&perimeter_and_gapfill_ranges,
|
||||||
|
// Infill regions to be sorted into islands.
|
||||||
|
&fill_expolygons, &fill_expolygons_bboxes, &fill_expolygons_ranges,
|
||||||
|
// Mapping of fill_expolygon to region and its infill.
|
||||||
|
&map_expolygon_to_region_and_fill,
|
||||||
|
// Output
|
||||||
|
®ions = m_regions, &lslices_ex = this->lslices_ex,
|
||||||
|
// fill_expolygons and fill_expolygons_bboxes need to be sorted into contiguous sequence by island,
|
||||||
|
// thus region_fill_sorted_last contains last fill_expolygon processed (meaning sorted).
|
||||||
|
®ion_fill_sorted_last]
|
||||||
|
(int lslice_idx, int source_slice_idx) {
|
||||||
|
lslices_ex[lslice_idx].islands.push_back({});
|
||||||
|
LayerIsland &island = lslices_ex[lslice_idx].islands.back();
|
||||||
|
island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first);
|
||||||
|
island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second;
|
||||||
|
if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) {
|
||||||
|
if (has_multiple_regions) {
|
||||||
|
// Check whether the fill expolygons of this island were split into multiple regions.
|
||||||
|
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
||||||
|
for (uint32_t fill_idx : fill_range) {
|
||||||
|
const std::pair<int, int> &kvp = map_expolygon_to_region_and_fill[fill_idx];
|
||||||
|
if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) {
|
||||||
|
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
island.fill_region_id = kvp.second;
|
||||||
|
}
|
||||||
|
if (island.fill_expolygons_composite()) {
|
||||||
|
// They were split, thus store the unsplit "composite" expolygons into the region of perimeters.
|
||||||
|
LayerRegion &this_layer_region = *regions[region_id];
|
||||||
|
auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size());
|
||||||
|
this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size());
|
||||||
|
std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite));
|
||||||
|
this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(),
|
||||||
|
fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end());
|
||||||
|
island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size()));
|
||||||
|
} else {
|
||||||
|
if (region_fill_sorted_last.empty())
|
||||||
|
region_fill_sorted_last.assign(regions.size(), 0);
|
||||||
|
uint32_t &last = region_fill_sorted_last[island.fill_region_id];
|
||||||
|
// They were not split and they belong to the same region.
|
||||||
|
// Sort the region m_fill_expolygons to a continuous span.
|
||||||
|
uint32_t begin = last;
|
||||||
|
LayerRegion &layerm = *regions[island.fill_region_id];
|
||||||
|
for (uint32_t fill_id : fill_range) {
|
||||||
|
uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second;
|
||||||
|
assert(region_fill_id >= last);
|
||||||
|
if (region_fill_id > last) {
|
||||||
|
std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]);
|
||||||
|
std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]);
|
||||||
|
}
|
||||||
|
++ last;
|
||||||
|
}
|
||||||
|
island.fill_expolygons = ExPolygonRange(begin, last);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Layer island is made of one fill region only.
|
||||||
|
island.fill_expolygons = fill_range;
|
||||||
|
island.fill_region_id = region_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// First sort into islands using exact fit.
|
||||||
|
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
||||||
|
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
||||||
|
auto point_inside_surface = [&lslices = this->lslices, &lslices_ex = this->lslices_ex](size_t lslice_idx, const Point &point) {
|
||||||
|
const BoundingBox &bbox = lslices_ex[lslice_idx].bbox;
|
||||||
|
return point.x() >= bbox.min.x() && point.x() < bbox.max.x() &&
|
||||||
|
point.y() >= bbox.min.y() && point.y() < bbox.max.y() &&
|
||||||
|
// Exact match: Don't just test whether a point is inside the outer contour of an island,
|
||||||
|
// test also whether the point is not inside some hole of the same expolygon.
|
||||||
|
// This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole
|
||||||
|
// and missed due to numerical issues.
|
||||||
|
lslices[lslice_idx].contains(point);
|
||||||
|
};
|
||||||
|
for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx)
|
||||||
for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice)
|
for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice)
|
||||||
if (point_inside_surface(lslice_idx, it_source_slice->second)) {
|
if (point_inside_surface(lslice_idx, it_source_slice->second)) {
|
||||||
this->lslices_ex[lslice_idx].islands.push_back({});
|
insert_into_island(lslice_idx, it_source_slice->first);
|
||||||
LayerIsland &island = this->lslices_ex[lslice_idx].islands.back();
|
if (std::next(it_source_slice) != perimeter_slices_queue.end())
|
||||||
const uint32_t source_slice_idx = it_source_slice->first;
|
|
||||||
island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first);
|
|
||||||
island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second;
|
|
||||||
if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) {
|
|
||||||
if (layer_region_ids.size() == 1) {
|
|
||||||
// Layer island is made of one fill region only.
|
|
||||||
island.fill_expolygons = fill_range;
|
|
||||||
island.fill_region_id = region_id;
|
|
||||||
} else {
|
|
||||||
// Check whether the fill expolygons of this island were split into multiple regions.
|
|
||||||
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
|
||||||
for (uint32_t fill_idx : fill_range) {
|
|
||||||
const std::pair<int, int> &kvp = map_expolygon_to_region_and_fill[fill_idx];
|
|
||||||
if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) {
|
|
||||||
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
|
||||||
break;
|
|
||||||
} else
|
|
||||||
island.fill_region_id = kvp.second;
|
|
||||||
}
|
|
||||||
if (island.fill_expolygons_composite()) {
|
|
||||||
// They were split, thus store the unsplit "composite" expolygons into the region of perimeters.
|
|
||||||
auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size());
|
|
||||||
this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size());
|
|
||||||
std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite));
|
|
||||||
this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(),
|
|
||||||
fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end());
|
|
||||||
island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size()));
|
|
||||||
} else {
|
|
||||||
if (region_fill_sorted_last.empty())
|
|
||||||
region_fill_sorted_last.assign(m_regions.size(), 0);
|
|
||||||
uint32_t &last = region_fill_sorted_last[island.fill_region_id];
|
|
||||||
// They were not split and they belong to the same region.
|
|
||||||
// Sort the region m_fill_expolygons to a continuous span.
|
|
||||||
uint32_t begin = last;
|
|
||||||
LayerRegion &layerm = *m_regions[island.fill_region_id];
|
|
||||||
for (uint32_t fill_id : fill_range) {
|
|
||||||
uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second;
|
|
||||||
assert(region_fill_id >= last);
|
|
||||||
if (region_fill_id > last) {
|
|
||||||
std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]);
|
|
||||||
std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]);
|
|
||||||
}
|
|
||||||
++ last;
|
|
||||||
}
|
|
||||||
island.fill_expolygons = ExPolygonRange(begin, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std::next(it_source_slice) != perimeter_slices_queue.end()) {
|
|
||||||
// Remove the current slice & point pair from the queue.
|
// Remove the current slice & point pair from the queue.
|
||||||
*it_source_slice = perimeter_slices_queue.back();
|
*it_source_slice = perimeter_slices_queue.back();
|
||||||
perimeter_slices_queue.pop_back();
|
perimeter_slices_queue.pop_back();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// If anything fails to be sorted in using exact fit, try to find a closest island.
|
||||||
|
auto point_inside_surface_dist2 =
|
||||||
|
[&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps = scaled<coord_t>(this->object()->print()->config().gcode_resolution.value) + SCALED_EPSILON]
|
||||||
|
(const size_t lslice_idx, const Point &point) {
|
||||||
|
const BoundingBox &bbox = lslices_ex[lslice_idx].bbox;
|
||||||
|
return
|
||||||
|
point.x() < bbox.min.x() - bbox_eps || point.x() > bbox.max.x() + bbox_eps ||
|
||||||
|
point.y() < bbox.min.y() - bbox_eps || point.y() > bbox.max.y() + bbox_eps ?
|
||||||
|
std::numeric_limits<double>::max() :
|
||||||
|
(lslices[lslice_idx].point_projection(point) - point).cast<double>().squaredNorm();
|
||||||
|
};
|
||||||
|
for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) {
|
||||||
|
double d2min = std::numeric_limits<double>::max();
|
||||||
|
auto it_source_slice = perimeter_slices_queue.end();
|
||||||
|
for (auto it = perimeter_slices_queue.begin(); it != perimeter_slices_queue.end(); ++ it)
|
||||||
|
if (double d2 = point_inside_surface_dist2(lslice_idx, it->second); d2 < d2min) {
|
||||||
|
d2min = d2;
|
||||||
|
it_source_slice = it;
|
||||||
|
}
|
||||||
|
assert(it_source_slice != perimeter_slices_queue.end());
|
||||||
|
if (it_source_slice != perimeter_slices_queue.end()) {
|
||||||
|
insert_into_island(lslice_idx, it_source_slice->first);
|
||||||
|
if (std::next(it_source_slice) != perimeter_slices_queue.end())
|
||||||
|
// Remove the current slice & point pair from the queue.
|
||||||
|
*it_source_slice = perimeter_slices_queue.back();
|
||||||
|
perimeter_slices_queue.pop_back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +79,8 @@ private:
|
|||||||
uint32_t m_region { 0 };
|
uint32_t m_region { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Most likely one LayerIsland will be filled with maximum one fill type.
|
// One LayerIsland may be filled with solid fill, sparse fill, top / bottom fill.
|
||||||
static constexpr const size_t LayerExtrusionRangesStaticSize = 1;
|
static constexpr const size_t LayerExtrusionRangesStaticSize = 3;
|
||||||
using LayerExtrusionRanges =
|
using LayerExtrusionRanges =
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
// To reduce memory allocation in release mode.
|
// To reduce memory allocation in release mode.
|
||||||
|
69
t/shells.t
69
t/shells.t
@ -1,4 +1,4 @@
|
|||||||
use Test::More tests => 20;
|
use Test::More tests => 17;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
@ -13,73 +13,6 @@ use Slic3r;
|
|||||||
use Slic3r::Geometry qw(epsilon);
|
use Slic3r::Geometry qw(epsilon);
|
||||||
use Slic3r::Test;
|
use Slic3r::Test;
|
||||||
|
|
||||||
{
|
|
||||||
my $config = Slic3r::Config::new_from_defaults;
|
|
||||||
$config->set('skirts', 0);
|
|
||||||
$config->set('perimeters', 0);
|
|
||||||
$config->set('solid_infill_speed', 99);
|
|
||||||
$config->set('top_solid_infill_speed', 99);
|
|
||||||
$config->set('bridge_speed', 72);
|
|
||||||
$config->set('first_layer_speed', '100%');
|
|
||||||
$config->set('cooling', [ 0 ]);
|
|
||||||
|
|
||||||
my $test = sub {
|
|
||||||
my ($conf) = @_;
|
|
||||||
$conf ||= $config;
|
|
||||||
|
|
||||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
|
||||||
|
|
||||||
my %z = (); # Z => 1
|
|
||||||
my %layers_with_solid_infill = (); # Z => $count
|
|
||||||
my %layers_with_bridge_infill = (); # Z => $count
|
|
||||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
|
||||||
my ($self, $cmd, $args, $info) = @_;
|
|
||||||
|
|
||||||
if ($self->Z > 0) {
|
|
||||||
$z{ $self->Z } = 1;
|
|
||||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
|
||||||
my $F = $args->{F} // $self->F;
|
|
||||||
$layers_with_solid_infill{$self->Z} = 1
|
|
||||||
if $F == $config->solid_infill_speed*60;
|
|
||||||
$layers_with_bridge_infill{$self->Z} = 1
|
|
||||||
if $F == $config->bridge_speed*60;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
my @z = sort { $a <=> $b } keys %z;
|
|
||||||
my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z;
|
|
||||||
fail "insufficient number of bottom solid layers"
|
|
||||||
unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]);
|
|
||||||
fail "excessive number of bottom solid layers"
|
|
||||||
unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers;
|
|
||||||
fail "insufficient number of top solid layers"
|
|
||||||
unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]);
|
|
||||||
fail "excessive number of top solid layers"
|
|
||||||
unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers;
|
|
||||||
if ($config->top_solid_layers > 0) {
|
|
||||||
fail "unexpected solid infill speed in first solid layer over sparse infill"
|
|
||||||
if $layers_with_solid_infill{ $z[-$config->top_solid_layers] };
|
|
||||||
die "bridge speed not used in first solid layer over sparse infill"
|
|
||||||
if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] };
|
|
||||||
}
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
$config->set('top_solid_layers', 3);
|
|
||||||
$config->set('bottom_solid_layers', 3);
|
|
||||||
ok $test->(), "proper number of shells is applied";
|
|
||||||
|
|
||||||
$config->set('top_solid_layers', 0);
|
|
||||||
$config->set('bottom_solid_layers', 0);
|
|
||||||
ok $test->(), "no shells are applied when both top and bottom are set to zero";
|
|
||||||
|
|
||||||
$config->set('perimeters', 1);
|
|
||||||
$config->set('top_solid_layers', 3);
|
|
||||||
$config->set('bottom_solid_layers', 3);
|
|
||||||
$config->set('fill_density', 0);
|
|
||||||
ok $test->(), "proper number of shells is applied even when fill density is none";
|
|
||||||
}
|
|
||||||
|
|
||||||
# issue #1161
|
# issue #1161
|
||||||
{
|
{
|
||||||
my $config = Slic3r::Config::new_from_defaults;
|
my $config = Slic3r::Config::new_from_defaults;
|
||||||
|
@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_print.cpp
|
test_print.cpp
|
||||||
test_printgcode.cpp
|
test_printgcode.cpp
|
||||||
test_printobject.cpp
|
test_printobject.cpp
|
||||||
|
test_shells.cpp
|
||||||
test_skirt_brim.cpp
|
test_skirt_brim.cpp
|
||||||
test_support_material.cpp
|
test_support_material.cpp
|
||||||
test_thin_walls.cpp
|
test_thin_walls.cpp
|
||||||
|
112
tests/fff_print/test_shells.cpp
Normal file
112
tests/fff_print/test_shells.cpp
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/GCodeReader.hpp"
|
||||||
|
|
||||||
|
#include "test_data.hpp" // get access to init_print, etc
|
||||||
|
|
||||||
|
using namespace Slic3r::Test;
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
SCENARIO("Shells", "[Shells]") {
|
||||||
|
GIVEN("20mm box") {
|
||||||
|
auto test = [](const DynamicPrintConfig &config){
|
||||||
|
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||||
|
|
||||||
|
std::vector<coord_t> zs;
|
||||||
|
std::set<coord_t> layers_with_solid_infill;
|
||||||
|
std::set<coord_t> layers_with_bridge_infill;
|
||||||
|
const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60;
|
||||||
|
const double bridge_speed = config.opt_float("bridge_speed") * 60;
|
||||||
|
GCodeReader parser;
|
||||||
|
parser.parse_buffer(gcode,
|
||||||
|
[&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed]
|
||||||
|
(Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||||
|
{
|
||||||
|
double z = line.new_Z(self);
|
||||||
|
REQUIRE(z >= 0);
|
||||||
|
if (z > 0) {
|
||||||
|
coord_t scaled_z = scaled<float>(z);
|
||||||
|
zs.emplace_back(scaled_z);
|
||||||
|
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||||
|
double f = line.new_F(self);
|
||||||
|
if (std::abs(f - solid_infill_speed) < EPSILON)
|
||||||
|
layers_with_solid_infill.insert(scaled_z);
|
||||||
|
if (std::abs(f - bridge_speed) < EPSILON)
|
||||||
|
layers_with_bridge_infill.insert(scaled_z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sort_remove_duplicates(zs);
|
||||||
|
|
||||||
|
auto has_solid_infill = [&layers_with_solid_infill](coord_t z) { return layers_with_solid_infill.find(z) != layers_with_solid_infill.end(); };
|
||||||
|
auto has_bridge_infill = [&layers_with_bridge_infill](coord_t z) { return layers_with_bridge_infill.find(z) != layers_with_bridge_infill.end(); };
|
||||||
|
auto has_shells = [&has_solid_infill, &has_bridge_infill, &zs](int layer_idx) { coord_t z = zs[layer_idx]; return has_solid_infill(z) || has_bridge_infill(z); };
|
||||||
|
const int bottom_solid_layers = config.opt_int("bottom_solid_layers");
|
||||||
|
const int top_solid_layers = config.opt_int("top_solid_layers");
|
||||||
|
THEN("correct number of bottom solid layers") {
|
||||||
|
for (int i = 0; i < bottom_solid_layers; ++ i)
|
||||||
|
REQUIRE(has_shells(i));
|
||||||
|
for (int i = bottom_solid_layers; i < int(zs.size() / 2); ++ i)
|
||||||
|
REQUIRE(! has_shells(i));
|
||||||
|
}
|
||||||
|
THEN("correct number of top solid layers") {
|
||||||
|
for (int i = 0; i < top_solid_layers; ++ i)
|
||||||
|
REQUIRE(has_shells(int(zs.size()) - i - 1));
|
||||||
|
for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i)
|
||||||
|
REQUIRE(! has_shells(int(zs.size()) - i - 1));
|
||||||
|
}
|
||||||
|
if (top_solid_layers > 0) {
|
||||||
|
THEN("solid infill speed is used on solid infill") {
|
||||||
|
for (int i = 0; i < top_solid_layers - 1; ++ i) {
|
||||||
|
auto z = zs[int(zs.size()) - i - 1];
|
||||||
|
REQUIRE(has_solid_infill(z));
|
||||||
|
REQUIRE(! has_bridge_infill(z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
THEN("bridge used in first solid layer over sparse infill") {
|
||||||
|
auto z = zs[int(zs.size()) - top_solid_layers];
|
||||||
|
REQUIRE(! has_solid_infill(z));
|
||||||
|
REQUIRE(has_bridge_infill(z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||||
|
{ "skirts", 0 },
|
||||||
|
{ "perimeters", 0 },
|
||||||
|
{ "solid_infill_speed", 99 },
|
||||||
|
{ "top_solid_infill_speed", 99 },
|
||||||
|
{ "bridge_speed", 72 },
|
||||||
|
{ "first_layer_speed", "100%" },
|
||||||
|
{ "cooling", "0" }
|
||||||
|
});
|
||||||
|
|
||||||
|
WHEN("three top and bottom layers") {
|
||||||
|
// proper number of shells is applied
|
||||||
|
config.set_deserialize_strict({
|
||||||
|
{ "top_solid_layers", 3 },
|
||||||
|
{ "bottom_solid_layers", 3 }
|
||||||
|
});
|
||||||
|
test(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("zero top and bottom layers") {
|
||||||
|
// no shells are applied when both top and bottom are set to zero
|
||||||
|
config.set_deserialize_strict({
|
||||||
|
{ "top_solid_layers", 0 },
|
||||||
|
{ "bottom_solid_layers", 0 }
|
||||||
|
});
|
||||||
|
test(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("three top and bottom layers, zero infill") {
|
||||||
|
// proper number of shells is applied even when fill density is none
|
||||||
|
config.set_deserialize_strict({
|
||||||
|
{ "perimeters", 1 },
|
||||||
|
{ "top_solid_layers", 3 },
|
||||||
|
{ "bottom_solid_layers", 3 }
|
||||||
|
});
|
||||||
|
test(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user