mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-26 04:12:07 +00:00
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:
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
72
src/libslic3r/LocalZOrderOptimizer.hpp
Normal file
72
src/libslic3r/LocalZOrderOptimizer.hpp
Normal 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
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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. "
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user