Merge branch 'main' into feature/h2c_support_clean_fixes

This commit is contained in:
SoftFever
2026-07-20 00:02:43 +08:00
committed by GitHub
630 changed files with 2317401 additions and 1240939 deletions

View File

@@ -51,6 +51,8 @@ using namespace nlohmann;
namespace Slic3r {
Print::SlicingPipelineHookFn Print::s_slicing_pipeline_hook_fn = nullptr;
template class PrintState<PrintStep, psCount>;
template class PrintState<PrintObjectStep, posCount>;
@@ -124,6 +126,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"printing_by_object_gcode",
"filament_end_gcode",
"post_process",
// "plugins" is the derived manifest backing the plugin-picker options; on its own it only
// affects G-code export. The specific option (e.g. slicing_pipeline_plugin) drives any re-slice.
"plugins",
"extruder_clearance_height_to_rod",
"extruder_clearance_height_to_lid",
"extruder_clearance_radius",
@@ -279,7 +284,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "initial_layer_print_height"
opt_key == "slicing_pipeline_plugin"
|| opt_key == "initial_layer_print_height"
|| opt_key == "nozzle_diameter"
|| opt_key == "filament_shrink"
|| opt_key == "filament_shrinkage_compensation_z"
@@ -2207,6 +2213,11 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (time_cost_with_cache)
*time_cost_with_cache = 0;
{
const auto* sp = this->config().option<ConfigOptionStrings>("slicing_pipeline_plugin");
m_pipeline_plugin_active = s_slicing_pipeline_hook_fn && sp && !sp->values.empty();
}
name_tbb_thread_pool_threads_set_locale();
//compute the PrintObject with the same geometries
@@ -2316,20 +2327,47 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
if (!use_cache) {
// Fire the SlicingPipeline hook for `obj` iff it just (re)computed `pstep` this pass.
auto hook_after = [this](PrintObject* obj, bool was_done, PrintObjectStep pstep, SlicingPipelineStepPlugin sstep) {
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(pstep))
run_pipeline_hook(sstep, obj);
};
// SlicingPipeline: dedicated slice loop so the Slice boundary is hookable before perimeters.
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
obj->make_perimeters();
}
else {
if (obj->set_started(posSlice))
obj->set_done(posSlice);
if (obj->set_started(posPerimeters))
obj->set_done(posPerimeters);
const bool was_done = obj->is_step_done(posSlice);
obj->slice();
hook_after(obj, was_done, posSlice, SlicingPipelineStepPlugin::posSlice);
// re-snapshot each layer's raw_slices AFTER the Slice hook ran, so the
// plugin's mutation becomes the untyped baseline. Without this, a later
// perimeter-only re-run (make_perimeters -> restore_untyped_slices) reverts
// slices to the PRE-hook geometry while posSlice stays cached (the hook does
// not re-fire), silently un-applying the mutation; raw_slices consumers
// (sharp-tail support, ToolOrdering) also read this backup directly. Gated on
// an active plugin AND a genuine (re)slice, so the inactive path is untouched
// and re-backing-up an unmutated layer is a harmless identical copy.
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(posSlice))
for (Layer *layer : obj->layers())
layer->backup_untyped_slices();
} else {
if (obj->set_started(posSlice)) obj->set_done(posSlice); // shared/duplicate — no hook
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posPerimeters);
obj->make_perimeters(); // slice() inside is a no-op: posSlice already DONE
hook_after(obj, was_done, posPerimeters, SlicingPipelineStepPlugin::posPerimeters);
} else {
if (obj->set_started(posPerimeters)) obj->set_done(posPerimeters);
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posEstimateCurledExtrusions);
obj->estimate_curled_extrusions();
hook_after(obj, was_done, posEstimateCurledExtrusions, SlicingPipelineStepPlugin::posEstimateCurledExtrusions);
}
else {
if (obj->set_started(posEstimateCurledExtrusions))
@@ -2338,7 +2376,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
// split prepare_infill (fill-surface prep) from infill (make_fills) so a
// plugin can mutate fill surfaces at the PrepareInfill seam and have make_fills
// consume them (unlike the Infill seam, which fires after the fills are already
// built). infill() re-invokes prepare_infill() as a no-op once posPrepareInfill
// is DONE, so this is a mechanical split mirroring the slice/perimeters loop.
const bool prepare_was_done = obj->is_step_done(posPrepareInfill);
obj->prepare_infill();
hook_after(obj, prepare_was_done, posPrepareInfill, SlicingPipelineStepPlugin::posPrepareInfill);
const bool was_done = obj->is_step_done(posInfill);
obj->infill();
hook_after(obj, was_done, posInfill, SlicingPipelineStepPlugin::posInfill);
}
else {
if (obj->set_started(posPrepareInfill))
@@ -2349,7 +2397,9 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posIroning);
obj->ironing();
hook_after(obj, was_done, posIroning, SlicingPipelineStepPlugin::posIroning);
}
else {
if (obj->set_started(posIroning))
@@ -2361,13 +2411,22 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->need_z_contouring();
if (need_contouring) {
const bool was_done = obj->is_step_done(posContouring);
obj->contour_z();
hook_after(obj, was_done, posContouring, SlicingPipelineStepPlugin::posContouring);
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
// SlicingPipeline: support runs in the parallel block below; the hook must fire in a
// sequential loop afterward. Snapshot per-object done-state just before the parallel_for.
std::vector<char> sup_was_done(m_objects.size(), 1);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
sup_was_done[i] = m_objects[i]->is_step_done(posSupportMaterial) ? 1 : 0;
tbb::parallel_for(tbb::blocked_range<int>(0, int(m_objects.size())),
[this, need_slicing_objects](const tbb::blocked_range<int>& range) {
for (int i = range.begin(); i < range.end(); i++) {
@@ -2383,9 +2442,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
if (need_slicing_objects.count(m_objects[i]) != 0 && !sup_was_done[i]
&& m_objects[i]->is_step_done(posSupportMaterial))
run_pipeline_hook(SlicingPipelineStepPlugin::posSupportMaterial, m_objects[i]);
for (PrintObject* obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posDetectOverhangsForLift);
obj->detect_overhangs_for_lift();
hook_after(obj, was_done, posDetectOverhangsForLift, SlicingPipelineStepPlugin::posDetectOverhangsForLift);
}
else {
if (obj->set_started(posDetectOverhangsForLift))
@@ -2462,6 +2529,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
this->set_done(psWipeTower);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psWipeTower, nullptr);
}
if (this->has_wipe_tower()) {
@@ -2478,8 +2546,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
m_skirt_brim_groups.clear();
m_has_shared_per_object_skirt = false;
m_skirt_convex_hull.clear();
m_objectBrimAreas.clear();
m_supportBrimAreas.clear();
m_objectBrimAreasByInstance.clear();
m_first_layer_convex_hull.points.clear();
for (PrintObject *object : m_objects) object->m_skirt.clear();
@@ -2635,14 +2702,16 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end())
objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID)));
}
// BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately
// Orca: Build both the old object-keyed brim map and the per-instance
// maps used by skirt/brim groups.
m_brimMap.clear();
m_supportBrimMap.clear();
m_brimMapByInstance.clear();
m_first_layer_convex_hull.points.clear();
if (this->has_brim()) {
Polygons islands_area;
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas);
m_brimMapByInstance, objPrintVec, printExtruders,
&m_objectBrimAreasByInstance);
for (Polygon& poly_ex : islands_area)
poly_ex.douglas_peucker(SCALED_RESOLUTION);
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
@@ -2664,6 +2733,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
this->finalize_first_layer_convex_hull();
this->set_done(psSkirtBrim);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psSkirtBrim, nullptr);
if (time_cost_with_cache) {
end_time = (long long)Slic3r::Utils::get_current_time_utc();
@@ -2674,7 +2744,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
if (((!use_cache)&&(need_slicing_objects.count(obj) != 0))
|| (use_cache &&(re_slicing_objects.count(obj) != 0))){
const bool was_done = obj->is_step_done(posSimplifyPath);
obj->simplify_extrusion_path();
// Unlike every other seam (all inside the `if (!use_cache)` block above), this loop is
// shared with the use_cache path (re_slicing_objects), so `!use_cache` must be checked
// explicitly here to keep hooks from ever firing on cache-loaded (plugin-final) objects.
if (!use_cache && m_pipeline_plugin_active && !was_done && obj->is_step_done(posSimplifyPath))
run_pipeline_hook(SlicingPipelineStepPlugin::posSimplifyPath, obj);
}
else {
if (obj->set_started(posSimplifyPath))
@@ -2784,7 +2860,8 @@ void Print::_make_skirt()
Polygon hull;
};
// Orca: build one local occupied hull per object from object and support geometry up to skirt height.
// Orca: Build one local occupied hull per object from object/support
// geometry up to skirt height. Instances translate this hull later.
std::vector<ObjectSkirtHull> object_convex_hulls;
for (PrintObject *object : m_objects) {
Points object_points;
@@ -2894,49 +2971,49 @@ void Print::_make_skirt()
object_hull.object->m_skirt.clear();
if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) {
struct SkirtGroupItem {
struct SkirtBrimGroupItem {
Points occupied_points;
ObjectID object_id;
size_t instance_id;
bool emits_skirt;
};
// Orca: group items represent occupied first-layer areas. Object items emit skirts;
// obstacle-only items, such as wipe tower, only force nearby object groups to merge.
std::vector<SkirtGroupItem> group_items;
// Orca: Each object instance can emit skirt/brim. Wipe tower is only an
// obstacle here; it may merge nearby items, but does not emit anything.
std::vector<SkirtBrimGroupItem> group_items;
const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing);
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
PrintObject* object = object_hull.object;
Points occupied_points;
for (const PrintInstance &instance : object->instances()) {
std::vector<size_t> object_item_indices;
object_item_indices.reserve(object->instances().size());
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
const PrintInstance &instance = object->instances()[instance_idx];
Points copy_points = object_hull.hull.points;
for (Point &pt : copy_points)
pt += instance.shift;
append(occupied_points, copy_points);
if (copy_points.size() < 3)
continue;
object_item_indices.push_back(group_items.size());
group_items.push_back({ std::move(copy_points), object->id(), instance_idx, true });
}
auto append_brim_points = [&occupied_points](const ExPolygons& areas) {
for (const ExPolygon& area : areas)
append(occupied_points, area.contour.points);
};
if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end())
append_brim_points(it->second);
if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end())
append_brim_points(it->second);
if (occupied_points.size() < 3)
continue;
// Orca: include the object's brim/support-brim footprint before checking skirt collisions.
group_items.push_back({ std::move(occupied_points), object->id(), true });
for (size_t item_idx : object_item_indices) {
const ObjectInstanceID key{ object->id(), group_items[item_idx].instance_id };
if (auto instance_brim_it = m_objectBrimAreasByInstance.find(key); instance_brim_it != m_objectBrimAreasByInstance.end())
for (const ExPolygon& area : instance_brim_it->second)
append(group_items[item_idx].occupied_points, area.contour.points);
}
}
// Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself.
Points wipe_tower_points = this->first_layer_wipe_tower_corners();
if (wipe_tower_points.size() >= 3)
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), false });
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), size_t(-1), false });
std::vector<size_t> parent(group_items.size());
std::iota(parent.begin(), parent.end(), 0);
// Orca: union-find keeps collision merging local without repeatedly rebuilding item lists.
// Orca: Use union-find so touching items can be merged while scanning.
auto find_parent = [&parent](size_t idx) {
while (parent[idx] != idx) {
parent[idx] = parent[parent[idx]];
@@ -2951,24 +3028,24 @@ void Print::_make_skirt()
parent[b] = a;
};
// Orca: combined skirt is the same grouping model with all items forced into one group.
// Orca: Combined skirt starts with all items in the same group.
if (m_config.skirt_type == stCombined && !group_items.empty())
for (size_t i = 1; i < group_items.size(); ++i)
unite(0, i);
auto build_grouped_points = [&]() {
struct GroupData {
Points points;
std::vector<ObjectID> object_ids;
bool emits_skirt = false;
auto build_skirt_brim_groups = [&]() {
struct SkirtBrimGroupData {
Points points;
std::vector<ObjectInstanceID> instances;
bool emits_skirt = false;
};
std::map<size_t, GroupData> grouped;
std::map<size_t, SkirtBrimGroupData> grouped;
for (size_t i = 0; i < group_items.size(); ++i) {
GroupData& group = grouped[find_parent(i)];
SkirtBrimGroupData& group = grouped[find_parent(i)];
append(group.points, group_items[i].occupied_points);
if (group_items[i].object_id.valid())
group.object_ids.push_back(group_items[i].object_id);
group.instances.push_back({ group_items[i].object_id, group_items[i].instance_id });
group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt;
}
return grouped;
@@ -2977,16 +3054,18 @@ void Print::_make_skirt()
bool groups_changed = m_config.skirt_type == stPerObject;
while (groups_changed) {
groups_changed = false;
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
std::vector<std::pair<size_t, Polygon>> group_envelopes;
for (const auto& [root, group] : grouped_points) {
if (group.points.size() < 3)
continue;
// Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not.
// Orca: Only skirt-emitting groups are expanded by skirt distance;
// obstacle-only groups stay at their occupied outline.
Polygon envelope = Geometry::convex_hull(group.points);
if (group.emits_skirt) {
// Orca: merge groups when a skirt envelope intersects another group or obstacle.
// Orca: If the expanded skirt outline touches another group
// or obstacle, merge them and run the pass again.
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
if (envelopes.empty())
continue;
@@ -3007,28 +3086,23 @@ void Print::_make_skirt()
}
}
auto make_group_brims = [this](const std::vector<ObjectID>& group_object_ids) {
auto make_brims_for_skirt_brim_group = [this](const std::vector<ObjectInstanceID>& group_instances) {
std::vector<SkirtBrimGroup::Brim> brims;
std::vector<ObjectID> brim_object_ids;
for (ObjectID object_id : group_object_ids) {
const auto brim_it = m_brimMap.find(object_id);
if (brim_it != m_brimMap.end() && !brim_it->second.empty())
brim_object_ids.push_back(object_id);
std::vector<ObjectInstanceID> brim_instances;
for (const ObjectInstanceID& instance : group_instances) {
const auto brim_it = m_brimMapByInstance.find(instance);
if (brim_it != m_brimMapByInstance.end() && !brim_it->second.empty())
brim_instances.push_back(instance);
}
const bool global_combined_brim = m_config.combine_brims && m_config.skirt_type != stPerObject && m_brimMap.size() == 1;
auto brim_owner_ids = [&group_object_ids, global_combined_brim](const std::vector<ObjectID>& object_ids) {
return global_combined_brim ? group_object_ids : object_ids;
};
const bool combine_group_brims = m_config.combine_brims && brim_object_ids.size() > 1;
const bool combine_group_brims = m_config.combine_brims && brim_instances.size() > 1;
if (!combine_group_brims) {
for (ObjectID object_id : brim_object_ids)
brims.push_back({ m_brimMap.at(object_id), brim_owner_ids({ object_id }) });
for (const ObjectInstanceID& instance : brim_instances)
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
return brims;
}
std::vector<size_t> brim_parent(brim_object_ids.size());
std::vector<size_t> brim_parent(brim_instances.size());
std::iota(brim_parent.begin(), brim_parent.end(), 0);
auto find_brim_parent = [&brim_parent](size_t idx) {
while (brim_parent[idx] != idx) {
@@ -3045,61 +3119,64 @@ void Print::_make_skirt()
};
const coord_t brim_contact_distance = coord_t(brim_flow().scaled_spacing() * 2.);
for (size_t i = 0; i < brim_object_ids.size(); ++i) {
const auto area_i = m_objectBrimAreas.find(brim_object_ids[i]);
if (area_i == m_objectBrimAreas.end())
for (size_t i = 0; i < brim_instances.size(); ++i) {
const auto area_i = m_objectBrimAreasByInstance.find(brim_instances[i]);
if (area_i == m_objectBrimAreasByInstance.end())
continue;
for (size_t j = i + 1; j < brim_object_ids.size(); ++j) {
const auto area_j = m_objectBrimAreas.find(brim_object_ids[j]);
if (area_j != m_objectBrimAreas.end() &&
for (size_t j = i + 1; j < brim_instances.size(); ++j) {
const auto area_j = m_objectBrimAreasByInstance.find(brim_instances[j]);
if (area_j != m_objectBrimAreasByInstance.end() &&
!intersection_ex(offset_ex(area_i->second, brim_contact_distance, jtRound, SCALED_RESOLUTION), area_j->second).empty())
unite_brims(i, j);
}
}
std::map<size_t, std::vector<ObjectID>> combined_brim_ids;
for (size_t i = 0; i < brim_object_ids.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_object_ids[i]);
std::map<size_t, std::vector<ObjectInstanceID>> combined_brim_ids;
for (size_t i = 0; i < brim_instances.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_instances[i]);
for (const auto& [_, object_ids] : combined_brim_ids) {
if (object_ids.size() == 1) {
brims.push_back({ m_brimMap.at(object_ids.front()), brim_owner_ids(object_ids) });
for (const auto& [_, instances] : combined_brim_ids) {
if (instances.size() == 1) {
const ObjectInstanceID& instance = instances.front();
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
continue;
}
ExPolygons combined_area;
for (ObjectID object_id : object_ids)
expolygons_append(combined_area, m_objectBrimAreas.at(object_id));
for (const ObjectInstanceID& instance : instances)
expolygons_append(combined_area, m_objectBrimAreasByInstance.at(instance));
combined_area = union_ex(combined_area);
const float scaled_resolution = float(scaled(m_config.resolution.value));
const float brim_cleanup_delta = std::max(scaled_resolution, float(SCALED_EPSILON));
combined_area = offset2_ex(combined_area, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
Polygons islands_area;
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), object_ids });
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), instances });
}
return brims;
};
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
for (auto& [_, group] : grouped_points) {
if (!group.emits_skirt || group.points.size() < 3)
continue;
if (generate_skirt && m_config.skirt_type == stPerObject && group.object_ids.size() > 1)
if (generate_skirt && m_config.skirt_type == stPerObject && group.instances.size() > 1)
m_has_shared_per_object_skirt = true;
// Orca: after merging, use the occupied outline directly; do not add skirt distance twice.
// Orca: Group points already include the occupied outline, so don't
// add skirt distance here again.
ExtrusionEntityCollection group_skirt;
if (generate_skirt)
append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true);
std::vector<SkirtBrimGroup::Brim> group_brims = make_group_brims(group.object_ids);
std::vector<SkirtBrimGroup::Brim> group_brims = make_brims_for_skirt_brim_group(group.instances);
if (!group_skirt.empty()) {
group_skirt.reverse();
// Orca: keep m_skirt as a flattened compatibility mirror for preview/extents.
// Orca: Keep m_skirt filled for code that still reads a flat
// skirt collection.
m_skirt.append(group_skirt.entities);
}
if (!group_skirt.empty() || !group_brims.empty())
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.object_ids), std::move(group_brims) });
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.instances), std::move(group_brims) });
}
}
}
@@ -4108,6 +4185,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4119,11 +4197,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
@@ -4219,6 +4300,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4230,11 +4312,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();