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:
Vojtech Bubnik 2022-11-07 14:47:43 +01:00
parent 6e653d9070
commit 8858651bf4
10 changed files with 602 additions and 733 deletions

View File

@ -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);
} }

View File

@ -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 &ltp : per_object[i]) {
ordering_item.print_z = ltp.print_z(); ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = &ltp - &front; ordering_item.layer_idx = &ltp - &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 &region = 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 &region) -> 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 &region = 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 &region = 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 &region : by_region)
if (! region.perimeters.empty()) {
m_config.apply(print.get_print_region(&region - &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 &region : 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(&region - &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

View File

@ -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;

View File

@ -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

View File

@ -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
}; };

View File

@ -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
&regions = 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).
&region_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();
}
} }
} }

View File

@ -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.

View File

@ -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;

View File

@ -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

View 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);
}
}
}