Add Local-Z wipe tower reserve planning and pass ordering

Add startup profiling for GUI initialization
Drop unusable Bonjour sockets and harden socket cleanup
Add /FS to MSVC parallel compile options
This commit is contained in:
Rad
2026-03-10 19:33:36 +01:00
parent 7c7d06a159
commit 153d35ded0
21 changed files with 961 additions and 132 deletions

View File

@@ -5,6 +5,7 @@
#include "libslic3r.h"
#include "I18N.hpp"
#include "GCode.hpp"
#include "LocalZOrderOptimizer.hpp"
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
@@ -12,6 +13,7 @@
#include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTower2.hpp"
#include "ShortestPath.hpp"
#include "Print.hpp"
#include "Utils.hpp"
@@ -428,7 +430,9 @@ static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
{
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
throw Slic3r::InvalidArgument(Slic3r::format(
"Error: WipeTowerIntegration::append_tcr unexpected toolchange: layer_idx=%1% tool_change_idx=%2% requested=%3% expected=%4% initial=%5%",
m_layer_idx, m_tool_change_idx, new_extruder_id, tcr.new_tool, tcr.initial_tool));
std::string gcode;
@@ -688,7 +692,9 @@ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::T
std::string WipeTowerIntegration::append_tcr2(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
{
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
throw Slic3r::InvalidArgument(Slic3r::format(
"Error: WipeTowerIntegration::append_tcr unexpected toolchange: layer_idx=%1% tool_change_idx=%2% requested=%3% expected=%4% initial=%5%",
m_layer_idx, m_tool_change_idx, new_extruder_id, tcr.new_tool, tcr.initial_tool));
std::string gcode;
@@ -922,25 +928,204 @@ std::string WipeTowerIntegration::prime(GCode& gcodegen)
return gcode;
}
std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned)
std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned,
double local_z_nominal_layer_z)
{
std::string gcode;
auto emit_local_z_unplanned_toolchange = [&]() -> std::string {
if (extruder_id < 0 || !gcodegen.writer().need_toolchange(extruder_id))
return "";
if (m_layer_idx < 0 || size_t(m_layer_idx) >= m_local_z_reserve_boxes.size() ||
size_t(m_layer_idx) >= m_local_z_reserve_slot_idx.size()) {
BOOST_LOG_TRIVIAL(debug) << "Local-Z unplanned toolchange using direct extruder switch"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " tool_change_idx=" << m_tool_change_idx;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
}
// Local-Z phase-b may introduce extra intra-layer toolchanges that were not part
// of the preplanned wipe tower sequence. Replaying a full wipe-tower template for
// each of those extra switches overprints the same tower layer at micro-step Zs
// and turns the tower into mush. Keep these extra switches local-Z-only by doing
// a direct toolchange here and leave the normal wipe tower plan untouched.
BOOST_LOG_TRIVIAL(debug) << "Local-Z unplanned toolchange using direct extruder switch"
size_t &slot_idx = m_local_z_reserve_slot_idx[size_t(m_layer_idx)];
const auto &layer_slots = m_local_z_reserve_boxes[size_t(m_layer_idx)];
if (slot_idx >= layer_slots.size() || layer_slots.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z toolchange reserve exhausted"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " reserved_slots=" << layer_slots.size()
<< " consumed_slots=" << slot_idx;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
}
const WipeTower::box_coordinates &slot = layer_slots[slot_idx++];
const double current_z = gcodegen.writer().get_position().z();
const double tower_z = local_z_nominal_layer_z >= 0. ? local_z_nominal_layer_z : current_z;
const double toolchange_print_z = tower_z - gcodegen.config().z_offset.value;
if (m_layer_idx >= (int)m_tool_changes.size() || m_tool_changes[m_layer_idx].empty()) {
BOOST_LOG_TRIVIAL(debug) << "Local-Z reserve has no matching wipe-tower layer metadata, using direct extruder switch"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id;
return gcodegen.set_extruder(unsigned(extruder_id), toolchange_print_z);
}
const float layer_height = m_tool_changes[m_layer_idx].front().layer_height;
if (gcodegen.m_curr_print != nullptr && gcodegen.writer().extruder() != nullptr) {
const Print& print = *gcodegen.m_curr_print;
const PrintConfig& print_config = print.config();
const size_t current_tool = gcodegen.writer().extruder()->id();
std::vector<std::vector<float>> wipe_volumes = WipeTower2::extract_wipe_volumes(print_config);
WipeTower2 local_z_wipe_tower(print_config,
print.default_region_config(),
print.get_plate_index(),
print.get_plate_origin(),
wipe_volumes,
current_tool);
for (size_t extruder_idx = 0; extruder_idx < print_config.nozzle_diameter.size(); ++extruder_idx)
local_z_wipe_tower.set_extruder(extruder_idx, print_config);
local_z_wipe_tower.set_current_tool(current_tool);
local_z_wipe_tower.set_layer(float(toolchange_print_z), layer_height, 0, m_layer_idx == 0, false);
WipeTower::ToolChangeResult local_z_tcr =
local_z_wipe_tower.local_z_tool_change(size_t(extruder_id), slot, float(print_config.prime_volume));
BOOST_LOG_TRIVIAL(debug) << "Local-Z toolchange emitted via wipe tower mini-toolchange"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " reserve_slot=" << (slot_idx - 1);
return gcodegen.is_BBL_Printer() ? append_tcr(gcodegen, local_z_tcr, extruder_id, tower_z) :
append_tcr2(gcodegen, local_z_tcr, extruder_id, tower_z);
}
const float nozzle_diameter = float(gcodegen.config().nozzle_diameter.get_at(size_t(extruder_id)));
const float line_width = nozzle_diameter * 1.25f;
const float extra_flow = float(gcodegen.config().wipe_tower_extra_flow.value) / 100.f;
const float extra_spacing = float(gcodegen.config().wipe_tower_extra_spacing.value) / 100.f;
const float filament_area = float((M_PI / 4.f) * std::pow(gcodegen.config().filament_diameter.get_at(size_t(extruder_id)), 2));
const float flow_per_mm =
filament_area > 0.f ?
(layer_height * (line_width - layer_height * float(1.f - M_PI / 4.f)) / filament_area) * std::max(0.f, extra_flow) :
0.f;
if (line_width <= 0.f || flow_per_mm <= 0.f)
return gcodegen.set_extruder(unsigned(extruder_id), toolchange_print_z);
const float inset_x = std::min(line_width, std::max(0.f, (slot.ru.x() - slot.ld.x()) * 0.25f));
const float inset_y = std::min(line_width * 0.5f, std::max(0.f, (slot.ru.y() - slot.rd.y()) * 0.25f));
const float x_min = slot.ld.x() + inset_x;
const float x_max = slot.rd.x() - inset_x;
const float y_min = slot.ld.y() + inset_y;
const float y_max = slot.lu.y() - inset_y;
if (x_max - x_min <= float(EPSILON) || y_max - y_min <= float(EPSILON))
return gcodegen.set_extruder(unsigned(extruder_id), toolchange_print_z);
std::vector<Vec2f> local_path;
local_path.reserve(16);
local_path.emplace_back(x_min, y_min);
local_path.emplace_back(x_max, y_min);
bool moving_left = true;
float y = y_min;
const float line_spacing = std::max(line_width, line_width * std::max(1.f, extra_spacing));
while (y + line_spacing < y_max - float(EPSILON)) {
y = std::min(y + line_spacing, y_max);
local_path.emplace_back(moving_left ? x_max : x_min, y);
local_path.emplace_back(moving_left ? x_min : x_max, y);
moving_left = !moving_left;
}
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
return Eigen::Rotation2Df(alpha) * pt + m_wipe_tower_pos;
};
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
const Vec2f start_pos = transform_wt_pt(local_path.front());
const Vec2f start_machine = start_pos + plate_origin_2d;
gcodegen.m_next_wipe_x = start_pos.x();
gcodegen.m_next_wipe_y = start_pos.y();
gcode += gcodegen.writer().unlift();
if (gcodegen.writer().extruder() != nullptr) {
auto type = ZHopType(gcodegen.m_config.z_hop_types.get_at(gcodegen.m_writer.extruder()->id()));
if (type == ZHopType::zhtAuto)
type = ZHopType::zhtSpiral;
if (gcodegen.m_config.z_hop_when_prime.get_at(gcodegen.m_writer.extruder()->id()))
gcode += gcodegen.retract(false, false, gcodegen.to_lift_type(type));
}
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_machine), erMixed,
"Travel to Local-Z wipe tower reserve");
gcode += gcodegen.unretract();
if (!is_approx(tower_z, current_z)) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(tower_z, "Travel to Local-Z tower layer");
gcode += gcodegen.writer().unretract();
}
gcode += gcodegen.set_extruder(unsigned(extruder_id), toolchange_print_z);
gcode += gcodegen.writer().travel_to_z(tower_z, "Force restore Local-Z tower Z", true);
{
Vec3d restored_position{gcodegen.writer().get_position()};
restored_position.z() = tower_z;
gcodegen.writer().set_position(restored_position);
}
gcode += gcodegen.unretract();
gcode += gcodegen.writer().travel_to_xy(start_machine.cast<double>(), "Return to Local-Z wipe tower reserve");
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + float_to_string_decimal_point(layer_height, 3) + "\n";
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) + ExtrusionEntity::role_to_string(erWipeTower) + "\n";
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + float_to_string_decimal_point(line_width, 3) + "\n";
double feedrate = std::max(1.0, double(gcodegen.config().wipe_tower_max_purge_speed.value)) * 60.0;
if (m_layer_idx == 0)
feedrate = std::min(feedrate, std::max(1.0, double(gcodegen.config().initial_layer_speed.value)) * 60.0);
gcode += gcodegen.writer().set_speed(feedrate, "Local-Z wipe tower reserve");
for (size_t point_idx = 1; point_idx < local_path.size(); ++point_idx) {
const Vec2f machine_pt = transform_wt_pt(local_path[point_idx]) + plate_origin_2d;
const double length = (local_path[point_idx] - local_path[point_idx - 1]).norm();
if (length <= EPSILON)
continue;
gcode += gcodegen.writer().extrude_to_xy(machine_pt.cast<double>(), flow_per_mm * float(length),
point_idx == 1 ? "Local-Z tower purge" : std::string());
}
const Vec2f end_machine = transform_wt_pt(local_path.back()) + plate_origin_2d;
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_machine));
gcodegen.m_wipe.reset_path();
for (size_t point_idx = local_path.size(); point_idx-- > 0;) {
const Vec2f machine_pt = transform_wt_pt(local_path[point_idx]) + plate_origin_2d;
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, machine_pt));
}
if (!is_approx(tower_z, current_z)) {
const Extruder *active_extruder = gcodegen.writer().extruder();
const bool can_wipe = active_extruder != nullptr &&
gcodegen.config().wipe.get_at(active_extruder->id()) &&
gcodegen.m_wipe.has_path() &&
scale_(gcodegen.config().wipe_distance.get_at(active_extruder->id())) > SCALED_EPSILON;
if (can_wipe) {
Wipe::RetractionValues wipe_retractions = gcodegen.m_wipe.calculateWipeRetractionLengths(gcodegen, false);
gcode += gcodegen.writer().retract(true, wipe_retractions.retractLengthBeforeWipe);
gcode += gcodegen.m_wipe.wipe(gcodegen, wipe_retractions.retractLengthDuringWipe, false, false);
}
gcode += gcodegen.writer().retract();
gcodegen.m_wipe.reset_path();
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back to Local-Z pass");
gcode += gcodegen.writer().unretract();
}
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
BOOST_LOG_TRIVIAL(debug) << "Local-Z toolchange emitted on wipe tower reserve"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " tool_change_idx=" << m_tool_change_idx;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
<< " reserve_slot=" << (slot_idx - 1);
return gcode;
};
if (local_z_unplanned)
@@ -2633,6 +2818,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream& file, ThumbnailsGenerato
if (has_wipe_tower && !layers_to_print.empty()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(),
*print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes,
print.wipe_tower_data().local_z_reserve_boxes,
*print.wipe_tower_data().final_purge.get()));
// BBS
file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height"));
@@ -5108,6 +5294,31 @@ LayerResult GCode::process_layer(const Print& print,
LocalZPassBucket* bucket { nullptr };
};
auto same_local_z_pass_group = [](const LocalZPassRef& lhs, const LocalZPassRef& rhs) {
assert(lhs.bucket != nullptr && rhs.bucket != nullptr);
assert(lhs.bucket->plan != nullptr && rhs.bucket->plan != nullptr);
return lhs.layer_to_print_idx == rhs.layer_to_print_idx &&
std::abs(lhs.bucket->plan->print_z - rhs.bucket->plan->print_z) <= EPSILON;
};
auto local_z_bucket_extruders = [](const LocalZPassBucket& bucket) {
std::vector<unsigned int> extruders;
extruders.reserve(bucket.by_extruder.size());
for (const auto& by_extruder_entry : bucket.by_extruder) {
if (!by_extruder_entry.second.empty())
extruders.push_back(by_extruder_entry.first);
}
return extruders;
};
auto shared_local_z_extruder = [](const std::vector<unsigned int>& lhs, const std::vector<unsigned int>& rhs) -> int {
for (unsigned int extruder_id : lhs) {
if (std::find(rhs.begin(), rhs.end(), extruder_id) != rhs.end())
return static_cast<int>(extruder_id);
}
return -1;
};
std::vector<LocalZPassRef> local_z_pass_refs;
if (local_z_perimeter_phase_b_enabled) {
for (size_t layer_to_print_idx = 0; layer_to_print_idx < local_z_layer_contexts.size(); ++layer_to_print_idx) {
@@ -5140,101 +5351,149 @@ LayerResult GCode::process_layer(const Print& print,
}
}
std::vector<unsigned int> layer_extruders = layer_tools.extruders;
for (const auto& by_extruder_entry : by_extruder) {
if (std::find(layer_extruders.begin(), layer_extruders.end(), by_extruder_entry.first) == layer_extruders.end())
layer_extruders.emplace_back(by_extruder_entry.first);
}
if (!local_z_pass_refs.empty()) {
const int local_z_phase_b_start_extruder =
(has_wipe_tower && m_writer.extruder() != nullptr) ? int(m_writer.extruder()->id()) : -1;
int local_z_phase_b_active_extruder = (m_writer.extruder() != nullptr) ? int(m_writer.extruder()->id()) : -1;
bool local_z_phase_b_changed_extruder = false;
BOOST_LOG_TRIVIAL(info) << "Local-Z phase-b emitting"
<< " print_z=" << print_z
<< " perimeter_passes=" << local_z_pass_refs.size();
gcode += "; local-z phase-b perimeter passes begin\n";
for (const LocalZPassRef& pass_ref : local_z_pass_refs) {
assert(pass_ref.bucket != nullptr && pass_ref.bucket->plan != nullptr);
const SubLayerPlan& pass_plan = *pass_ref.bucket->plan;
const double pass_z = pass_plan.print_z + m_config.z_offset.value;
const double saved_nominal_z = m_nominal_z;
const float saved_last_layer_z = m_last_layer_z;
// Ensure all travel/lift logic inside this pass references the micro-pass Z,
// not the base layer nominal Z.
m_nominal_z = pass_z;
m_last_layer_z = float(pass_z);
BOOST_LOG_TRIVIAL(debug) << "Local-Z pass emit"
<< " print_z=" << print_z
<< " layer_to_print_idx=" << pass_ref.layer_to_print_idx
<< " layer_id=" << pass_plan.layer_id
<< " pass_index=" << pass_plan.pass_index
<< " pass_print_z=" << pass_plan.print_z
<< " pass_flow_height=" << pass_plan.flow_height
<< " extruder_buckets=" << pass_ref.bucket->by_extruder.size();
if (std::abs(m_writer.get_position().z() - pass_z) > EPSILON) {
gcode += this->retract(false, false, LiftType::NormalLift);
gcode += m_writer.travel_to_z(pass_z, "Local-Z perimeter pass");
size_t pass_ref_idx = 0;
while (pass_ref_idx < local_z_pass_refs.size()) {
size_t pass_group_end = pass_ref_idx + 1;
while (pass_group_end < local_z_pass_refs.size() &&
same_local_z_pass_group(local_z_pass_refs[pass_ref_idx], local_z_pass_refs[pass_group_end])) {
++pass_group_end;
}
for (auto& by_extruder_entry : pass_ref.bucket->by_extruder) {
const unsigned int local_extruder_id = by_extruder_entry.first;
std::vector<ObjectByExtruder>& objects_by_extruder = by_extruder_entry.second;
if (objects_by_extruder.empty())
continue;
std::vector<std::vector<unsigned int>> pass_group_extruders;
pass_group_extruders.reserve(pass_group_end - pass_ref_idx);
for (size_t group_idx = pass_ref_idx; group_idx < pass_group_end; ++group_idx)
pass_group_extruders.push_back(local_z_bucket_extruders(*local_z_pass_refs[group_idx].bucket));
if (has_wipe_tower && m_writer.need_toolchange(local_extruder_id))
local_z_phase_b_changed_extruder = true;
if (has_wipe_tower && m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, int(local_extruder_id), false, true);
// Local-Z phase-b uses the wipe tower outside the normal per-layer
// extruder loop, so mirror the usual toolchange bookkeeping here.
// This forces the next object path to refresh WIDTH/HEIGHT tags
// after prime tower G-code, keeping the preview in sync with the
// actual local-Z pass height.
m_last_processor_extrusion_role = erWipeTower;
} else {
gcode += this->set_extruder(local_extruder_id, pass_plan.print_z);
}
// Buckets that land on the same Local-Z plane are independent, so prefer
// whichever one lets us keep the currently active extruder.
const std::vector<size_t> ordered_pass_group =
LocalZOrderOptimizer::order_pass_group(pass_group_extruders, local_z_phase_b_active_extruder);
for (size_t ordered_group_idx = 0; ordered_group_idx < ordered_pass_group.size(); ++ordered_group_idx) {
const size_t group_local_idx = ordered_pass_group[ordered_group_idx];
const LocalZPassRef& pass_ref = local_z_pass_refs[pass_ref_idx + group_local_idx];
assert(pass_ref.bucket != nullptr && pass_ref.bucket->plan != nullptr);
const SubLayerPlan& pass_plan = *pass_ref.bucket->plan;
const double pass_z = pass_plan.print_z + m_config.z_offset.value;
const double saved_nominal_z = m_nominal_z;
const float saved_last_layer_z = m_last_layer_z;
// Ensure all travel/lift logic inside this pass references the micro-pass Z,
// not the base layer nominal Z.
m_nominal_z = pass_z;
m_last_layer_z = float(pass_z);
BOOST_LOG_TRIVIAL(debug) << "Local-Z pass emit"
<< " print_z=" << print_z
<< " layer_to_print_idx=" << pass_ref.layer_to_print_idx
<< " layer_id=" << pass_plan.layer_id
<< " pass_index=" << pass_plan.pass_index
<< " pass_print_z=" << pass_plan.print_z
<< " pass_flow_height=" << pass_plan.flow_height
<< " extruder_buckets=" << pass_ref.bucket->by_extruder.size();
if (std::abs(m_writer.get_position().z() - pass_z) > EPSILON) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z pass z restore"
<< " print_z=" << print_z
<< " layer_id=" << pass_plan.layer_id
<< " pass_index=" << pass_plan.pass_index
<< " extruder=" << local_extruder_id
<< " expected_pass_z=" << pass_z
<< " observed_z_after_toolchange=" << m_writer.get_position().z();
gcode += m_writer.travel_to_z(pass_z, "Local-Z pass z restore");
gcode += this->retract(false, false, LiftType::NormalLift);
gcode += m_writer.travel_to_z(pass_z, "Local-Z perimeter pass");
}
std::vector<InstanceToPrint> instances_to_print =
sort_print_object_instances(objects_by_extruder, layers, ordering, single_object_instance_idx);
for (InstanceToPrint& instance_to_print : instances_to_print) {
const LayerToPrint& layer_to_print = layers[instance_to_print.layer_id];
const 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();
const std::vector<unsigned int>& group_bucket_extruders = pass_group_extruders[group_local_idx];
int preferred_last_extruder = -1;
if (ordered_group_idx + 1 < ordered_pass_group.size()) {
preferred_last_extruder =
shared_local_z_extruder(group_bucket_extruders,
pass_group_extruders[ordered_pass_group[ordered_group_idx + 1]]);
}
const std::vector<unsigned int> ordered_bucket_extruders =
LocalZOrderOptimizer::order_bucket_extruders(group_bucket_extruders,
local_z_phase_b_active_extruder,
preferred_last_extruder);
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;
const bool saved_reduce_crossing_wall = m_config.reduce_crossing_wall.value;
// Local-Z phase-b emits many short micro-passes. Avoid-crossing
// travel planning is expensive and fragile on these fragments, so
// keep travel simple here.
m_config.reduce_crossing_wall.value = false;
m_avoid_crossing_perimeters.disable_once();
for (unsigned int local_extruder_id : ordered_bucket_extruders) {
auto by_extruder_it = pass_ref.bucket->by_extruder.find(local_extruder_id);
if (by_extruder_it == pass_ref.bucket->by_extruder.end())
continue;
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));
std::vector<ObjectByExtruder>& objects_by_extruder = by_extruder_it->second;
if (objects_by_extruder.empty())
continue;
for (ObjectByExtruder::Island& island : instance_to_print.object_by_extruder.islands) {
gcode += this->extrude_perimeters(print, island.by_region, first_layer, false);
gcode += this->extrude_perimeters(print, island.by_region, first_layer, true);
if (has_wipe_tower && m_writer.need_toolchange(local_extruder_id))
local_z_phase_b_changed_extruder = true;
if (has_wipe_tower && m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, int(local_extruder_id), false, true, print_z + m_config.z_offset.value);
// Local-Z phase-b uses the wipe tower outside the normal per-layer
// extruder loop, so mirror the usual toolchange bookkeeping here.
// This forces the next object path to refresh WIDTH/HEIGHT tags
// after prime tower G-code, keeping the preview in sync with the
// actual local-Z pass height.
m_last_processor_extrusion_role = erWipeTower;
} else {
gcode += this->set_extruder(local_extruder_id, pass_plan.print_z);
}
m_config.reduce_crossing_wall.value = saved_reduce_crossing_wall;
if (std::abs(m_writer.get_position().z() - pass_z) > EPSILON) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z pass z restore"
<< " print_z=" << print_z
<< " layer_id=" << pass_plan.layer_id
<< " pass_index=" << pass_plan.pass_index
<< " extruder=" << local_extruder_id
<< " expected_pass_z=" << pass_z
<< " observed_z_after_toolchange=" << m_writer.get_position().z();
gcode += m_writer.travel_to_z(pass_z, "Local-Z pass z restore");
}
std::vector<InstanceToPrint> instances_to_print =
sort_print_object_instances(objects_by_extruder, layers, ordering, single_object_instance_idx);
for (InstanceToPrint& instance_to_print : instances_to_print) {
const LayerToPrint& layer_to_print = layers[instance_to_print.layer_id];
const 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;
const bool saved_reduce_crossing_wall = m_config.reduce_crossing_wall.value;
// Local-Z phase-b emits many short micro-passes. Avoid-crossing
// travel planning is expensive and fragile on these fragments, so
// keep travel simple here.
m_config.reduce_crossing_wall.value = false;
m_avoid_crossing_perimeters.disable_once();
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));
for (ObjectByExtruder::Island& island : instance_to_print.object_by_extruder.islands) {
gcode += this->extrude_perimeters(print, island.by_region, first_layer, false);
gcode += this->extrude_perimeters(print, island.by_region, first_layer, true);
}
m_config.reduce_crossing_wall.value = saved_reduce_crossing_wall;
}
local_z_phase_b_active_extruder = int(local_extruder_id);
}
m_nominal_z = saved_nominal_z;
m_last_layer_z = saved_last_layer_z;
}
m_nominal_z = saved_nominal_z;
m_last_layer_z = saved_last_layer_z;
pass_ref_idx = pass_group_end;
}
// Keep wipe tower planning synchronized with the normal per-layer extruder loop:
@@ -5248,7 +5507,8 @@ LayerResult GCode::process_layer(const Print& print,
<< " restore_extruder=" << local_z_phase_b_start_extruder;
gcode += "; local-z phase-b restore pre-pass extruder for wipe tower\n";
if (m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, local_z_phase_b_start_extruder, false, true);
gcode += m_wipe_tower->tool_change(*this, local_z_phase_b_start_extruder, false, true,
print_z + m_config.z_offset.value);
m_last_processor_extrusion_role = erWipeTower;
} else {
gcode += this->set_extruder(static_cast<unsigned int>(local_z_phase_b_start_extruder), print_z);
@@ -5266,12 +5526,6 @@ LayerResult GCode::process_layer(const Print& print,
}
gcode += "; local-z phase-b perimeter passes end\n";
}
std::vector<unsigned int> layer_extruders = layer_tools.extruders;
for (const auto& by_extruder_entry : by_extruder) {
if (std::find(layer_extruders.begin(), layer_extruders.end(), by_extruder_entry.first) == layer_extruders.end())
layer_extruders.emplace_back(by_extruder_entry.first);
}
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
for (unsigned int extruder_id : layer_extruders) {
if (print.config().skirt_type == stCombined && !print.skirt().empty())

View File

@@ -79,6 +79,7 @@ public:
const Vec3d plate_origin,
const std::vector<WipeTower::ToolChangeResult> &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const std::vector<std::vector<WipeTower::box_coordinates>> &local_z_reserve_boxes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.prime_tower_width.value)),
@@ -87,9 +88,11 @@ public:
m_extruder_offsets(print_config.extruder_offset.values),
m_priming(priming),
m_tool_changes(tool_changes),
m_local_z_reserve_boxes(local_z_reserve_boxes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0),
m_local_z_reserve_slot_idx(local_z_reserve_boxes.size(), 0),
m_plate_origin(plate_origin),
m_single_extruder_multi_material(print_config.single_extruder_multi_material),
m_enable_timelapse_print(print_config.timelapse_type.value == TimelapseType::tlSmooth),
@@ -97,10 +100,16 @@ public:
{}
std::string prime(GCode &gcodegen);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
void next_layer() {
++ m_layer_idx;
m_tool_change_idx = 0;
if (m_layer_idx >= 0 && size_t(m_layer_idx) < m_local_z_reserve_slot_idx.size())
m_local_z_reserve_slot_idx[size_t(m_layer_idx)] = 0;
}
// If local_z_unplanned is true, emit a wipe/toolchange without consuming the preplanned
// per-layer wipe-tower sequence (used by Local-Z phase-b extra toolchanges).
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned = false);
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned = false,
double local_z_nominal_layer_z = -1.);
bool is_empty_wipe_tower_gcode(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;
@@ -127,10 +136,12 @@ private:
// Reference to cached values at the Printer class.
const std::vector<WipeTower::ToolChangeResult> &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const std::vector<std::vector<WipeTower::box_coordinates>> &m_local_z_reserve_boxes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
std::vector<size_t> m_local_z_reserve_slot_idx;
double m_last_wipe_tower_print_z = 0.f;
// BBS

View File

@@ -263,6 +263,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
{
m_is_BBL_printer = object.print()->is_BBL_printer();
m_print_full_config = &object.print()->full_print_config();
m_print_config_ptr = &object.print()->config();
m_print_object_ptr = &object;
// Mixed filament support.
m_mixed_mgr = &object.print()->mixed_filament_manager();
@@ -1033,7 +1034,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume()
return false;
};
std::optional<unsigned int>current_extruder_id;
std::optional<unsigned int> current_extruder_id;
for (int i = 0; i < m_layer_tools.size(); ++i) {
LayerTools& lt = m_layer_tools[i];
if (lt.extruders.empty())

View File

@@ -1278,6 +1278,7 @@ WipeTower2::WipeTower2(const PrintConfig& config,
, m_extra_flow(float(config.wipe_tower_extra_flow / 100.))
, m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing / 100. * config.wipe_tower_extra_flow / 100.))
, m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing / 100.))
, m_local_z_wipe_tower_purge_lines(float(config.local_z_wipe_tower_purge_lines))
, m_y_shift(0.f)
, m_z_pos(0.f)
, m_bridging(float(config.wipe_tower_bridging))
@@ -1612,6 +1613,63 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
return construct_tcr(writer, false, old_tool, false);
}
WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool,
const WipeTower::box_coordinates& cleaning_box,
float wipe_volume)
{
const size_t old_tool = m_current_tool;
WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model);
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.append(";--------------------\n"
"; CP TOOLCHANGE START\n");
writer.comment_with_value(" toolchange #", m_num_tool_changes + 1);
writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " +
m_filpar[new_tool].material + "\n")
.c_str())
.append(";--------------------\n");
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n");
writer.speed_override_backup();
writer.speed_override(100);
writer.set_initial_position(cleaning_box.ld, m_wipe_tower_width, m_wipe_tower_depth, 0.f);
if (m_set_extruder_trimpot)
writer.set_extruder_trimpot(750);
const int old_tool_temp = is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature;
const int new_tool_temp = is_first_layer() ? m_filpar[new_tool].first_layer_temperature : m_filpar[new_tool].temperature;
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, old_tool_temp, new_tool_temp);
toolchange_Change(writer, new_tool, m_filpar[new_tool].material);
toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(), writer.y() - m_perimeter_width);
toolchange_Wipe(writer, cleaning_box, wipe_volume);
writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n");
++m_num_tool_changes;
if (m_set_extruder_trimpot)
writer.set_extruder_trimpot(550);
writer.speed_override_restore();
writer.feedrate(m_travel_speed * 60.f)
.flush_planner_queue()
.reset_extruder()
.append("; CP TOOLCHANGE END\n"
";------------------\n"
"\n\n");
if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
WipeTower::ToolChangeResult result = construct_tcr(writer, false, old_tool, false);
result.purge_volume = wipe_volume;
return result;
}
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer,
const WipeTower::box_coordinates& cleaning_box,
@@ -1991,9 +2049,11 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
// If spare layers are excluded -> if 1 or less toolchange has been done, it must be still the first layer, too. So slow down.
bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers);
float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f);
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth - (current_depth - m_perimeter_width)),
m_wipe_tower_width - 2 * m_perimeter_width, current_depth - m_perimeter_width);
float reserve_depth = m_layer_info->local_z_reserve_depth();
float current_depth = std::max(0.f, m_layer_info->depth - m_layer_info->toolchanges_depth() - reserve_depth);
float fill_depth = std::max(0.f, current_depth - m_perimeter_width);
WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth - fill_depth),
m_wipe_tower_width - 2 * m_perimeter_width, fill_depth);
writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
@@ -2242,6 +2302,54 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
}
void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_t reserve_slot_count, float wipe_volume)
{
if (reserve_slot_count == 0)
return;
assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON);
if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par)
m_plan.push_back(WipeTowerInfo(z_par, layer_height_par));
const float mini_wipe_depth = m_local_z_wipe_tower_purge_lines * m_perimeter_width * m_extra_spacing_wipe;
const float wipe_width = std::max(0.f, m_wipe_tower_width - 3.f * m_perimeter_width);
const float wiping_depth = wipe_width > WT_EPSILON ?
get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, m_perimeter_width, m_extra_flow,
m_extra_spacing_wipe, wipe_width) :
0.f;
float max_ramming_depth = 0.f;
if (wipe_width > WT_EPSILON) {
for (const FilamentParameters& filament : m_filpar) {
const bool do_ramming = (m_semm && m_enable_filament_ramming) || filament.multitool_ramming;
if (!do_ramming || filament.ramming_speed.empty())
continue;
const float line_width = m_perimeter_width * filament.ramming_line_width_multiplicator;
const float line_step =
(m_perimeter_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) *
m_extra_spacing_ramming;
if (line_width <= WT_EPSILON || line_step <= WT_EPSILON)
continue;
const float ramming_volume = 0.25f * std::accumulate(filament.ramming_speed.begin(), filament.ramming_speed.end(), 0.f);
const float length_to_extrude = volume_to_length(ramming_volume, line_width, layer_height_par);
const float ramming_depth =
(float(int(length_to_extrude / wipe_width) + 1) * line_step);
max_ramming_depth = std::max(max_ramming_depth, ramming_depth);
}
}
const float full_toolchange_depth = max_ramming_depth + wiping_depth;
const float slot_depth =
std::max(2.5f * m_perimeter_width, std::max(mini_wipe_depth + m_perimeter_width, full_toolchange_depth + m_perimeter_width));
WipeTowerInfo &layer = m_plan.back();
layer.local_z_reserve_slot_depth = std::max(layer.local_z_reserve_slot_depth, slot_depth);
layer.local_z_reserve_slot_count += reserve_slot_count;
}
void WipeTower2::plan_tower()
{
// Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards
@@ -2252,7 +2360,7 @@ void WipeTower2::plan_tower()
m_current_height = 0.f;
for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) {
float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth());
float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].planned_depth());
m_plan[layer_index].depth = this_layer_depth;
if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
@@ -2431,6 +2539,64 @@ std::vector<std::pair<float, float>> WipeTower2::get_z_and_depth_pairs() const
return out;
}
static Vec2f rotate_local_z_reserve_point(const Vec2f& pt, float tower_width, float tower_depth, float y_shift, float internal_angle_deg)
{
Vec2f shifted = pt;
shifted.x() -= tower_width / 2.f;
shifted.y() += y_shift - tower_depth / 2.f;
const double angle = internal_angle_deg * double(M_PI / 180.);
const double c = std::cos(angle);
const double s = std::sin(angle);
return Vec2f(float(shifted.x() * c - shifted.y() * s) + tower_width / 2.f,
float(shifted.x() * s + shifted.y() * c) + tower_depth / 2.f);
}
std::vector<std::vector<WipeTower::box_coordinates>> WipeTower2::get_local_z_reserve_boxes() const
{
std::vector<std::vector<WipeTower::box_coordinates>> out;
out.reserve(m_plan.size());
for (size_t layer_idx = 0; layer_idx < m_plan.size(); ++layer_idx) {
const WipeTowerInfo& layer = m_plan[layer_idx];
std::vector<WipeTower::box_coordinates> layer_boxes;
layer_boxes.reserve(layer.local_z_reserve_slot_count);
if (layer.local_z_reserve_slot_count > 0 && layer.local_z_reserve_slot_depth > WT_EPSILON) {
const float y_shift = layer.depth < m_wipe_tower_depth - m_perimeter_width ?
(m_wipe_tower_depth - layer.depth - m_perimeter_width) / 2.f : 0.f;
const float internal_angle = (layer_idx % 2 == 0) ? 180.f : 0.f;
for (size_t slot_idx = 0; slot_idx < layer.local_z_reserve_slot_count; ++slot_idx) {
const float slot_start = layer.toolchanges_depth() + float(slot_idx) * layer.local_z_reserve_slot_depth;
const float width = std::max(0.f, m_wipe_tower_width - 2.f * m_perimeter_width);
const float height = std::max(0.f, layer.local_z_reserve_slot_depth - m_perimeter_width);
if (width <= WT_EPSILON || height <= WT_EPSILON)
continue;
const Vec2f ld_unrotated(m_perimeter_width, slot_start + m_perimeter_width);
const Vec2f rd_unrotated = ld_unrotated + Vec2f(width, 0.f);
const Vec2f ru_unrotated = ld_unrotated + Vec2f(width, height);
const Vec2f lu_unrotated = ld_unrotated + Vec2f(0.f, height);
const Vec2f ld = rotate_local_z_reserve_point(ld_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f rd = rotate_local_z_reserve_point(rd_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f ru = rotate_local_z_reserve_point(ru_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const Vec2f lu = rotate_local_z_reserve_point(lu_unrotated, m_wipe_tower_width, m_wipe_tower_depth, y_shift, internal_angle);
const float min_x = std::min({ld.x(), rd.x(), ru.x(), lu.x()});
const float max_x = std::max({ld.x(), rd.x(), ru.x(), lu.x()});
const float min_y = std::min({ld.y(), rd.y(), ru.y(), lu.y()});
const float max_y = std::max({ld.y(), rd.y(), ru.y(), lu.y()});
layer_boxes.emplace_back(Vec2f(min_x, min_y), max_x - min_x, max_y - min_y);
}
}
out.emplace_back(std::move(layer_boxes));
}
return out;
}
Polygon WipeTower2::generate_rib_polygon(const WipeTower::box_coordinates& wt_box)
{
auto get_current_layer_rib_len = [](float cur_height, float max_height, float max_len) -> float {

View File

@@ -50,12 +50,14 @@ public:
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f);
void plan_local_z_reserve(float z_par, float layer_height_par, size_t reserve_slot_count, float wipe_volume = 0.f);
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; }
std::vector<std::pair<float, float>> get_z_and_depth_pairs() const;
std::vector<std::vector<WipeTower::box_coordinates>> get_local_z_reserve_boxes() const;
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
float get_wipe_tower_height() const { return m_wipe_tower_height; }
@@ -118,6 +120,8 @@ public:
// Returns gcode for a toolchange and a final print head position.
// On the first layer, extrude a brim around the future wipe tower first.
WipeTower::ToolChangeResult tool_change(size_t new_tool);
WipeTower::ToolChangeResult local_z_tool_change(size_t new_tool, const WipeTower::box_coordinates& cleaning_box, float wipe_volume);
void set_current_tool(size_t tool) { m_current_tool = tool; }
// Fill the unfilled space with a sparse infill.
// Call this method only if layer_finished() is false.
@@ -254,6 +258,7 @@ private:
float m_extra_flow = 1.f;
float m_extra_spacing_wipe = 1.f;
float m_extra_spacing_ramming = 1.f;
float m_local_z_wipe_tower_purge_lines = 3.f;
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
@@ -289,6 +294,10 @@ private:
float height; // layer height
float depth; // depth of the layer based on all layers above
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
float local_z_reserve_slot_depth { 0.f };
size_t local_z_reserve_slot_count { 0 };
float local_z_reserve_depth() const { return local_z_reserve_slot_depth * float(local_z_reserve_slot_count); }
float planned_depth() const { return toolchanges_depth() + local_z_reserve_depth(); }
std::vector<ToolChange> tool_changes;

View File

@@ -0,0 +1,72 @@
#ifndef slic3r_LocalZOrderOptimizer_hpp_
#define slic3r_LocalZOrderOptimizer_hpp_
#include <algorithm>
#include <numeric>
#include <vector>
namespace Slic3r {
namespace LocalZOrderOptimizer {
inline bool bucket_contains_extruder(const std::vector<unsigned int> &extruders, int extruder_id)
{
return extruder_id >= 0 &&
std::find(extruders.begin(), extruders.end(), static_cast<unsigned int>(extruder_id)) != extruders.end();
}
inline std::vector<unsigned int> order_bucket_extruders(std::vector<unsigned int> extruders,
int current_extruder,
int preferred_last_extruder = -1)
{
extruders.erase(std::unique(extruders.begin(), extruders.end()), extruders.end());
if (extruders.empty())
return extruders;
if (current_extruder >= 0) {
auto current_it = std::find(extruders.begin(), extruders.end(), static_cast<unsigned int>(current_extruder));
if (current_it != extruders.end())
std::rotate(extruders.begin(), current_it, extruders.end());
}
if (preferred_last_extruder >= 0 && extruders.size() > 1 && static_cast<int>(extruders.front()) != preferred_last_extruder) {
auto preferred_it = std::find(extruders.begin() + 1, extruders.end(), static_cast<unsigned int>(preferred_last_extruder));
if (preferred_it != extruders.end())
std::rotate(preferred_it, preferred_it + 1, extruders.end());
}
return extruders;
}
inline std::vector<size_t> order_pass_group(const std::vector<std::vector<unsigned int>> &group_extruders, int current_extruder)
{
std::vector<size_t> remaining(group_extruders.size());
std::iota(remaining.begin(), remaining.end(), size_t(0));
std::vector<size_t> ordered;
ordered.reserve(group_extruders.size());
int active_extruder = current_extruder;
while (!remaining.empty()) {
auto next_it = std::find_if(remaining.begin(), remaining.end(), [&](size_t idx) {
return bucket_contains_extruder(group_extruders[idx], active_extruder);
});
if (next_it == remaining.end())
next_it = remaining.begin();
const size_t next_idx = *next_it;
ordered.push_back(next_idx);
const std::vector<unsigned int> ordered_bucket = order_bucket_extruders(group_extruders[next_idx], active_extruder);
if (!ordered_bucket.empty())
active_extruder = static_cast<int>(ordered_bucket.back());
remaining.erase(next_it);
}
return ordered;
}
} // namespace LocalZOrderOptimizer
} // namespace Slic3r
#endif

View File

@@ -883,7 +883,7 @@ static std::vector<std::string> s_Preset_print_options {
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
"initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width",
"make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes",
"wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed",
"wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed", "local_z_wipe_tower_purge_lines",
"wipe_tower_wall_type", "wipe_tower_extra_rib_length", "wipe_tower_rib_width", "wipe_tower_fillet_wall",
"wipe_tower_filament", "wiping_volumes_extruders","wipe_tower_bridging", "wipe_tower_extra_flow","single_extruder_multi_material_priming",
"wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic",

View File

@@ -9,6 +9,9 @@
#include "common_func/common_func.hpp"
#include <algorithm>
#include <chrono>
#include <cctype>
#include <cstdlib>
#include <set>
#include <fstream>
#include <unordered_map>
@@ -32,6 +35,30 @@
namespace Slic3r {
namespace {
bool startup_profile_enabled()
{
static const bool enabled = [] {
const char* value = std::getenv("ORCA_STARTUP_PROFILE");
if (value == nullptr)
return false;
std::string normalized(value);
std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return normalized == "1" || normalized == "true" || normalized == "yes" || normalized == "on";
}();
return enabled;
}
void startup_profile_log(const std::string& message)
{
if (startup_profile_enabled())
BOOST_LOG_TRIVIAL(warning) << "[StartupProfile] " << message;
}
} // namespace
static std::vector<std::string> s_project_options {
"flush_volumes_vector",
"flush_volumes_matrix",
@@ -250,6 +277,10 @@ void PresetBundle::copy_files(const std::string& from)
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
{
const bool startup_profile = startup_profile_enabled();
const auto total_start = std::chrono::steady_clock::now();
auto phase_start = total_start;
// First load the vendor specific system presets.
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
@@ -258,6 +289,13 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, substitution_rule %1%, preferred printer_model_id %2%")%substitution_rule%preferred_selection.printer_model_id;
//BBS: change system config to json
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
if (startup_profile) {
const auto now = std::chrono::steady_clock::now();
startup_profile_log("PresetBundle::load_presets step=load_system_presets_from_json step_ms=" +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - phase_start).count()) +
" total_ms=" + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - total_start).count()));
phase_start = now;
}
// BBS load preset from user's folder, load system default if
// BBS: change directories by design
@@ -267,16 +305,34 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
} else {
load_user_presets(dir_user_presets, substitution_rule);
}
if (startup_profile) {
const auto now = std::chrono::steady_clock::now();
startup_profile_log("PresetBundle::load_presets step=load_user_presets step_ms=" +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - phase_start).count()) +
" total_ms=" + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - total_start).count()));
phase_start = now;
}
this->update_multi_material_filament_presets();
this->update_compatible(PresetSelectCompatibleType::Never);
this->load_selections(config, preferred_selection);
if (startup_profile) {
const auto now = std::chrono::steady_clock::now();
startup_profile_log("PresetBundle::load_presets step=post_load_selection step_ms=" +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - phase_start).count()) +
" total_ms=" + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now - total_start).count()));
}
set_calibrate_printer("");
//BBS: add config related logs
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
if (startup_profile) {
const auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - total_start).count();
startup_profile_log("PresetBundle::load_presets end substitutions=" + std::to_string(substitutions.size()) +
" total_ms=" + std::to_string(total_ms));
}
return substitutions;
}
@@ -1204,6 +1260,9 @@ void PresetBundle::remove_users_preset(AppConfig &config, std::map<std::string,
//BBS: add json related logic, load system presets from json
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
const bool startup_profile = startup_profile_enabled();
const auto total_start = std::chrono::steady_clock::now();
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule;
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
@@ -1245,6 +1304,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
for (auto &vendor_name : vendor_names)
{
const auto vendor_start = std::chrono::steady_clock::now();
if (validation_mode && !vendor_to_validate.empty() && vendor_name != vendor_to_validate && vendor_name != ORCA_FILAMENT_LIBRARY)
continue;
@@ -1279,6 +1339,12 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
errors_cummulative += "\n";
}
}
if (startup_profile) {
const auto vendor_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - vendor_start).count();
startup_profile_log("PresetBundle::load_system_presets_from_json vendor=" + vendor_name +
" vendor_ms=" + std::to_string(vendor_ms));
}
}
if (first) {
@@ -1286,9 +1352,14 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
this->reset(false);
}
this->update_system_maps();
this->update_system_maps();
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, errors_cummulative %1%")%errors_cummulative;
if (startup_profile) {
const auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - total_start).count();
startup_profile_log("PresetBundle::load_system_presets_from_json end vendor_count=" + std::to_string(vendor_names.size()) +
" total_ms=" + std::to_string(total_ms));
}
return std::make_pair(std::move(substitutions), errors_cummulative);
}
@@ -2729,6 +2800,9 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_from_json(
const std::string &path, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle)
{
const bool startup_profile = startup_profile_enabled();
const auto total_start = std::chrono::steady_clock::now();
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
ConfigSubstitutionContext substitution_context { compatibility_rule };
PresetsConfigSubstitutions substitutions;
@@ -2830,6 +2904,14 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
//goto __error_process;
}
if (startup_profile) {
startup_profile_log("PresetBundle::load_vendor_configs_from_json vendor=" + vendor_name +
" machine_models=" + std::to_string(machine_model_subfiles.size()) +
" process=" + std::to_string(process_subfiles.size()) +
" filaments=" + std::to_string(filament_subfiles.size()) +
" machines=" + std::to_string(machine_subfiles.size()));
}
if (flags.has(LoadConfigBundleAttribute::LoadFilamentOnly)) {
machine_model_subfiles.clear();
machine_subfiles.clear();
@@ -3171,6 +3253,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
presets = &this->prints;
configs.clear();
filament_id_maps.clear();
auto process_start = std::chrono::steady_clock::now();
for (auto& subfile : process_subfiles)
{
std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded);
@@ -3182,12 +3265,18 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
throw ConfigurationError((boost::format("Failed loading configuration file %1%\nSuggest cleaning the directory %2% firstly") % subfile_path % path).str());
}
}
if (startup_profile) {
const auto process_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - process_start).count();
startup_profile_log("PresetBundle::load_vendor_configs_from_json vendor=" + vendor_name +
" section=process section_ms=" + std::to_string(process_ms));
}
//3.2) paste the filaments
presets = &this->filaments;
configs.clear();
filament_id_maps.clear();
const auto is_orca_lib = vendor_name == ORCA_FILAMENT_LIBRARY;
auto filament_start = std::chrono::steady_clock::now();
for (auto& subfile : filament_subfiles)
{
std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets,
@@ -3200,6 +3289,11 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
throw ConfigurationError((boost::format("Failed loading configuration file %1%\nSuggest cleaning the directory %2% firstly") % subfile_path % path).str());
}
}
if (startup_profile) {
const auto filament_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - filament_start).count();
startup_profile_log("PresetBundle::load_vendor_configs_from_json vendor=" + vendor_name +
" section=filament section_ms=" + std::to_string(filament_ms));
}
if (is_orca_lib) {
m_config_maps = configs;
m_filament_id_maps = filament_id_maps;
@@ -3209,6 +3303,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
presets = &this->printers;
configs.clear();
filament_id_maps.clear();
auto machine_start = std::chrono::steady_clock::now();
for (auto& subfile : machine_subfiles)
{
std::string reason = parse_subfile(substitution_context, substitutions, flags, subfile, configs, filament_id_maps, presets, presets_loaded);
@@ -3220,6 +3315,14 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
throw ConfigurationError((boost::format("Failed loading configuration file %1%\nSuggest cleaning the directory %2% firstly") % subfile_path % path).str());
}
}
if (startup_profile) {
const auto machine_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - machine_start).count();
const auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - total_start).count();
startup_profile_log("PresetBundle::load_vendor_configs_from_json vendor=" + vendor_name +
" section=machine section_ms=" + std::to_string(machine_ms) +
" presets_loaded=" + std::to_string(presets_loaded) +
" total_ms=" + std::to_string(total_ms));
}
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", finished, presets_loaded %1%")%presets_loaded;

View File

@@ -52,6 +52,50 @@ template class PrintState<PrintObjectStep, posCount>;
PrintRegion::PrintRegion(const PrintRegionConfig &config) : PrintRegion(config, config.hash()) {}
PrintRegion::PrintRegion(PrintRegionConfig &&config) : PrintRegion(std::move(config), config.hash()) {}
static size_t estimate_local_z_wipe_tower_reserve_slots(const PrintObject& print_object, coordf_t print_z)
{
const Layer* object_layer = print_object.get_layer_at_printz(print_z, EPSILON);
if (object_layer == nullptr)
return 0;
const auto& intervals = print_object.local_z_intervals();
const auto& plans = print_object.local_z_sublayer_plan();
if (intervals.empty() || plans.empty())
return 0;
const size_t layer_id = size_t(object_layer->id());
const auto interval_it = std::find_if(intervals.begin(), intervals.end(), [layer_id](const LocalZInterval& interval) {
return interval.layer_id == layer_id;
});
if (interval_it == intervals.end() || !interval_it->has_mixed_paint || interval_it->sublayer_count <= 1 ||
interval_it->first_sublayer_idx >= plans.size()) {
return 0;
}
const size_t first_idx = interval_it->first_sublayer_idx;
const size_t end_idx = std::min(plans.size(), first_idx + interval_it->sublayer_count);
size_t reserve_slots = 0;
int previous_extruder = -1;
for (size_t plan_idx = first_idx; plan_idx < end_idx; ++plan_idx) {
const SubLayerPlan& plan = plans[plan_idx];
if (!plan.split_interval)
continue;
for (size_t extruder_id = 0; extruder_id < plan.painted_masks_by_extruder.size(); ++extruder_id) {
if (plan.painted_masks_by_extruder[extruder_id].empty())
continue;
if (previous_extruder != int(extruder_id)) {
++reserve_slots;
previous_extruder = int(extruder_id);
}
}
}
if (reserve_slots > 0)
++reserve_slots;
return reserve_slots;
}
//BBS
// ORCA: Now this is a parameter
//float Print::min_skirt_length = 0;
@@ -2879,6 +2923,17 @@ void Print::_make_wipe_tower()
current_extruder_id = extruder_id;
}
}
if (m_config.dithering_local_z_mode) {
size_t local_z_reserve_slots = 0;
for (const PrintObject* print_object : m_objects)
local_z_reserve_slots += estimate_local_z_wipe_tower_reserve_slots(*print_object, layer_tools.print_z);
if (local_z_reserve_slots > 0) {
wipe_tower.plan_local_z_reserve((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height,
local_z_reserve_slots, (float) m_config.prime_volume);
}
}
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
break;
@@ -2890,6 +2945,7 @@ void Print::_make_wipe_tower()
wipe_tower.generate(m_wipe_tower_data.tool_changes);
m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs();
m_wipe_tower_data.local_z_reserve_boxes = wipe_tower.get_local_z_reserve_boxes();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height();

View File

@@ -778,6 +778,7 @@ struct WipeTowerData
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth;
std::vector<std::pair<float, float>> z_and_depth_pairs;
std::vector<std::vector<WipeTower::box_coordinates>> local_z_reserve_boxes;
float brim_width;
float height;
@@ -788,6 +789,7 @@ struct WipeTowerData
used_filament.clear();
number_of_toolchanges = -1;
depth = 0.f;
local_z_reserve_boxes.clear();
brim_width = 0.f;
}

View File

@@ -6026,6 +6026,16 @@ void PrintConfigDef::init_fff_params()
def->max = 300.;
def->set_default_value(new ConfigOptionPercent(100.));
def = this->add("local_z_wipe_tower_purge_lines", coFloat);
def->label = L("Local-Z mini wipe lines");
def->tooltip = L("Number of purge lines reserved for each runtime Local-Z wipe tower toolchange. "
"Higher values improve cleanup but increase tower depth. "
"Only used when Local-Z dithering and the prime tower are enabled.");
def->sidetext = L("lines");
def->mode = comAdvanced;
def->min = 1.0;
def->set_default_value(new ConfigOptionFloat(3.0));
def = this->add("idle_temperature", coInts);
def->label = L("Idle temperature");
def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups. "

View File

@@ -1403,6 +1403,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, prime_tower_brim_chamfer_max_width))
((ConfigOptionFloat, wipe_tower_bridging))
((ConfigOptionPercent, wipe_tower_extra_flow))
((ConfigOptionFloat, local_z_wipe_tower_purge_lines))
((ConfigOptionFloats, flush_volumes_matrix))
((ConfigOptionFloats, flush_volumes_vector))