mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Purge tower part 1
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"error_string": "Success.",
|
||||||
|
"export_time": 0,
|
||||||
|
"layer_height": 0.0,
|
||||||
|
"plate_index": 0,
|
||||||
|
"prepare_time": 0,
|
||||||
|
"return_code": 0,
|
||||||
|
"sparse_infill_density": 0.0,
|
||||||
|
"wall_loops": 0
|
||||||
|
}
|
||||||
@@ -381,6 +381,10 @@ bool ToolOrdering::insert_wipe_tower_extruder()
|
|||||||
{
|
{
|
||||||
if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
|
if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
|
||||||
return false;
|
return false;
|
||||||
|
// Belt mode has no classic wipe tower; the dedicated wipe tower filament
|
||||||
|
// must not inject extra toolchanges into the purge prism planning.
|
||||||
|
if (m_print_config_ptr->belt_printer)
|
||||||
|
return false;
|
||||||
if (m_print_config_ptr->wipe_tower_filament == 0)
|
if (m_print_config_ptr->wipe_tower_filament == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -1160,6 +1160,8 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"prime_volume",
|
"prime_volume",
|
||||||
"prime_tower_infill_gap",
|
"prime_tower_infill_gap",
|
||||||
"prime_tower_flat_ironing",
|
"prime_tower_flat_ironing",
|
||||||
|
"belt_purge_tower_width",
|
||||||
|
"belt_purge_tower_object",
|
||||||
"enable_tower_interface_features",
|
"enable_tower_interface_features",
|
||||||
"enable_tower_interface_cooldown_during_tower",
|
"enable_tower_interface_cooldown_during_tower",
|
||||||
"wipe_tower_no_sparse_layers",
|
"wipe_tower_no_sparse_layers",
|
||||||
|
|||||||
+216
-7
@@ -22,6 +22,7 @@
|
|||||||
#include "MaterialType.hpp"
|
#include "MaterialType.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
|
#include "LocalesUtils.hpp"
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -389,6 +390,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "prime_volume"
|
|| opt_key == "prime_volume"
|
||||||
|| opt_key == "flush_into_infill"
|
|| opt_key == "flush_into_infill"
|
||||||
|| opt_key == "flush_into_support"
|
|| opt_key == "flush_into_support"
|
||||||
|
|| opt_key == "belt_purge_tower_width"
|
||||||
|| opt_key == "initial_layer_infill_speed"
|
|| opt_key == "initial_layer_infill_speed"
|
||||||
|| opt_key == "travel_speed"
|
|| opt_key == "travel_speed"
|
||||||
|| opt_key == "travel_speed_z"
|
|| opt_key == "travel_speed_z"
|
||||||
@@ -1473,6 +1475,15 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_config.enable_prime_tower) {
|
if (m_config.enable_prime_tower) {
|
||||||
|
if (m_config.belt_printer.value && m_config.print_sequence == PrintSequence::ByObject
|
||||||
|
&& extruders.size() > 1 && warning != nullptr) {
|
||||||
|
StringObjectException warningtemp;
|
||||||
|
warningtemp.string = L("The belt purge tower is not generated in \"By object\" print sequence; "
|
||||||
|
"filament changes will not be purged.");
|
||||||
|
warningtemp.opt_key = "enable_prime_tower";
|
||||||
|
warningtemp.is_warning = true;
|
||||||
|
*warning = warningtemp;
|
||||||
|
}
|
||||||
for (const PrintObject* object : m_objects) {
|
for (const PrintObject* object : m_objects) {
|
||||||
if (object->config().precise_z_height.value) {
|
if (object->config().precise_z_height.value) {
|
||||||
warn(L("Enabling both precise Z height and the prime tower may cause slicing errors."), "precise_z_height");
|
warn(L("Enabling both precise Z height and the prime tower may cause slicing errors."), "precise_z_height");
|
||||||
@@ -1596,7 +1607,7 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
return {_u8L("Variable layer height is not supported with Organic supports.") };
|
return {_u8L("Variable layer height is not supported with Organic supports.") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_wipe_tower() && ! m_objects.empty()) {
|
if ((this->has_wipe_tower() || this->has_belt_purge_tower()) && ! m_objects.empty()) {
|
||||||
// Make sure all extruders use same diameter filament and have the same nozzle diameter
|
// Make sure all extruders use same diameter filament and have the same nozzle diameter
|
||||||
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
|
// EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments
|
||||||
double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front());
|
double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front());
|
||||||
@@ -1612,12 +1623,17 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! m_config.use_relative_e_distances)
|
// The following two constraints come from the classic wipe tower G-code
|
||||||
return { L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).") };
|
// generator; purging into the belt purge prism uses normal object
|
||||||
|
// extrusions and does not need them.
|
||||||
|
if (this->has_wipe_tower()) {
|
||||||
|
if (! m_config.use_relative_e_distances)
|
||||||
|
return { L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).") };
|
||||||
|
|
||||||
|
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
||||||
|
return {L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.")};
|
||||||
|
}
|
||||||
|
|
||||||
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
|
||||||
return {L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.")};
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
|
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
|
||||||
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware)
|
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware)
|
||||||
@@ -2708,7 +2724,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
|
|
||||||
m_wipe_tower_data.clear();
|
m_wipe_tower_data.clear();
|
||||||
m_tool_ordering.clear();
|
m_tool_ordering.clear();
|
||||||
if (this->has_wipe_tower()) {
|
if (this->has_belt_purge_tower() && this->config().print_sequence != PrintSequence::ByObject) {
|
||||||
|
this->_plan_belt_purge();
|
||||||
|
}
|
||||||
|
else if (this->has_wipe_tower()) {
|
||||||
this->_make_wipe_tower();
|
this->_make_wipe_tower();
|
||||||
}
|
}
|
||||||
else if (this->config().print_sequence != PrintSequence::ByObject) {
|
else if (this->config().print_sequence != PrintSequence::ByObject) {
|
||||||
@@ -4085,6 +4104,13 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
|
|||||||
// Wipe tower support.
|
// Wipe tower support.
|
||||||
bool Print::has_wipe_tower() const
|
bool Print::has_wipe_tower() const
|
||||||
{
|
{
|
||||||
|
// Belt printers never get the classic wipe tower: its G-code is generated
|
||||||
|
// directly in machine XY coordinates and bypasses the belt rotation
|
||||||
|
// transform. Purging is routed into the belt purge prism instead
|
||||||
|
// (see has_belt_purge_tower() / _plan_belt_purge()).
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (m_config.enable_prime_tower.value == true) {
|
if (m_config.enable_prime_tower.value == true) {
|
||||||
if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
|
if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
|
||||||
return true;
|
return true;
|
||||||
@@ -4097,6 +4123,16 @@ bool Print::has_wipe_tower() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt purge prism: purging after filament changes is routed into a sliced
|
||||||
|
// prism object via the flush-into-objects machinery instead of a wipe tower.
|
||||||
|
bool Print::has_belt_purge_tower() const
|
||||||
|
{
|
||||||
|
return m_config.belt_printer.value
|
||||||
|
&& m_config.enable_prime_tower.value
|
||||||
|
&& !m_config.spiral_mode.value
|
||||||
|
&& m_config.filament_diameter.values.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
||||||
{
|
{
|
||||||
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
|
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
|
||||||
@@ -4183,6 +4219,179 @@ bool Print::enable_timelapse_print() const
|
|||||||
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
|
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt mode: snap the purge prism's layer grid onto the printed objects' grid.
|
||||||
|
// After belt slicing every object's layer print_z carries a per-object global
|
||||||
|
// z offset (mesh-vertex-scan belt_z_shift + instance-Y-dependent terms), so
|
||||||
|
// objects at different belt-Y positions do not share a layer grid. Purge
|
||||||
|
// marking looks absorbers up with get_layer_at_printz(lt.print_z, EPSILON),
|
||||||
|
// so the prism only absorbs purge at toolchange print_z values that coincide
|
||||||
|
// with one of its own layers. Shifting the prism by at most half a layer
|
||||||
|
// (a sub-layer-height displacement along the belt) makes its grid residue
|
||||||
|
// match the reference object's; both grids step by the same layer height
|
||||||
|
// (enforced by validate()), so matching residues means exact layer matches.
|
||||||
|
void Print::_align_belt_purge_layers()
|
||||||
|
{
|
||||||
|
PrintObject *prism = nullptr;
|
||||||
|
for (PrintObject *po : m_objects)
|
||||||
|
if (po->config().belt_purge_tower_object.value) {
|
||||||
|
prism = po;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (prism == nullptr || prism->layers().empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double h = prism->config().layer_height.value;
|
||||||
|
if (h <= EPSILON)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Grid residue of an object's layer grid: identical for all of an object's
|
||||||
|
// layers above the first since they step by h.
|
||||||
|
auto grid_offset = [h](const PrintObject *po) {
|
||||||
|
const double z = po->layers().front()->print_z;
|
||||||
|
return z - std::floor(z / h) * h; // in [0, h)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reference grid: the tallest non-prism object (proxy for the object with
|
||||||
|
// the most toolchange layers).
|
||||||
|
const PrintObject *ref = nullptr;
|
||||||
|
double ref_top = -std::numeric_limits<double>::max();
|
||||||
|
for (const PrintObject *po : m_objects) {
|
||||||
|
if (po->config().belt_purge_tower_object.value || po->layers().empty())
|
||||||
|
continue;
|
||||||
|
const double top = po->layers().back()->print_z;
|
||||||
|
if (top > ref_top) {
|
||||||
|
ref_top = top;
|
||||||
|
ref = po;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ref == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double ref_offset = grid_offset(ref);
|
||||||
|
bool grids_mismatch = false;
|
||||||
|
for (const PrintObject *po : m_objects) {
|
||||||
|
if (po == ref || po->config().belt_purge_tower_object.value || po->layers().empty())
|
||||||
|
continue;
|
||||||
|
double d = std::abs(grid_offset(po) - ref_offset);
|
||||||
|
d = std::min(d, h - d);
|
||||||
|
if (d > 5. * EPSILON) {
|
||||||
|
grids_mismatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift normalized to (-h/2, h/2].
|
||||||
|
double delta = ref_offset - grid_offset(prism);
|
||||||
|
if (delta > 0.5 * h)
|
||||||
|
delta -= h;
|
||||||
|
else if (delta <= -0.5 * h)
|
||||||
|
delta += h;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] purge prism grid snap"
|
||||||
|
<< " ref=" << ref->model_object()->name
|
||||||
|
<< " ref_offset=" << ref_offset
|
||||||
|
<< " prism_offset=" << grid_offset(prism)
|
||||||
|
<< " delta=" << delta
|
||||||
|
<< " grids_mismatch=" << grids_mismatch;
|
||||||
|
|
||||||
|
prism->belt_shift_layer_grid(delta);
|
||||||
|
|
||||||
|
if (grids_mismatch)
|
||||||
|
this->active_step_add_warning(
|
||||||
|
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||||
|
_u8L("Objects on the plate are sliced on different layer grids; the belt purge tower can only follow "
|
||||||
|
"one of them. Filament changes on layers of the other objects may not be fully purged."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belt mode replacement for _make_wipe_tower(): plan filament-change purging
|
||||||
|
// into the belt purge prism (and any other flush_into_* object) using the
|
||||||
|
// flush-into-objects machinery, without generating classic wipe tower G-code.
|
||||||
|
// The toolchange itself is emitted by GCode::set_extruder() via the
|
||||||
|
// change_filament_gcode macro; the overrides marked here make the new
|
||||||
|
// filament's first extrusions land in the purge prism.
|
||||||
|
void Print::_plan_belt_purge()
|
||||||
|
{
|
||||||
|
m_wipe_tower_data.clear();
|
||||||
|
|
||||||
|
// Must run before ToolOrdering is built: LayerTools merge per-object layer
|
||||||
|
// print_z values, and the prism only absorbs purge where its (snapped)
|
||||||
|
// layers coincide with the toolchange layers.
|
||||||
|
this->_align_belt_purge_layers();
|
||||||
|
|
||||||
|
const unsigned int number_of_extruders = (unsigned int) m_config.filament_colour.values.size();
|
||||||
|
|
||||||
|
// No initial priming extrusions: there is no tower to prime on.
|
||||||
|
m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int) -1, false);
|
||||||
|
m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int) -1, false);
|
||||||
|
|
||||||
|
if (m_wipe_tower_data.tool_ordering.empty() || m_wipe_tower_data.tool_ordering.last_extruder() == unsigned(-1))
|
||||||
|
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
|
||||||
|
|
||||||
|
if (!m_wipe_tower_data.tool_ordering.has_wipe_tower())
|
||||||
|
// No toolchanges anywhere, nothing to purge.
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->throw_if_canceled();
|
||||||
|
|
||||||
|
// Flush volumes per filament pair, mirroring the generic wipe tower path:
|
||||||
|
// full flush matrix for single extruder multi material with purging enabled,
|
||||||
|
// plain prime volume otherwise.
|
||||||
|
std::vector<float> flush_matrix(cast<float>(
|
||||||
|
get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size())));
|
||||||
|
std::vector<std::vector<float>> wipe_volumes;
|
||||||
|
for (unsigned int i = 0; i < number_of_extruders; ++i)
|
||||||
|
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders,
|
||||||
|
flush_matrix.begin() + (i + 1) * number_of_extruders));
|
||||||
|
const bool use_flush_matrix = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
|
||||||
|
const float flush_multiplier = (float) m_config.flush_multiplier.get_at(0);
|
||||||
|
|
||||||
|
float total_leftover = 0.f;
|
||||||
|
float worst_layer_leftover = 0.f;
|
||||||
|
double worst_layer_z = 0.;
|
||||||
|
|
||||||
|
unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||||
|
for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) {
|
||||||
|
float layer_leftover = 0.f;
|
||||||
|
for (const unsigned int extruder_id : layer_tools.extruders) {
|
||||||
|
if (extruder_id == current_extruder_id)
|
||||||
|
continue;
|
||||||
|
float volume_to_wipe = use_flush_matrix ?
|
||||||
|
wipe_volumes[current_extruder_id][extruder_id] * flush_multiplier :
|
||||||
|
(float) m_config.prime_volume;
|
||||||
|
float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id,
|
||||||
|
volume_to_wipe);
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] purge toolchange print_z=" << layer_tools.print_z
|
||||||
|
<< " filament " << current_extruder_id << "->" << extruder_id
|
||||||
|
<< " requested=" << volume_to_wipe
|
||||||
|
<< " absorbed=" << volume_to_wipe - leftover
|
||||||
|
<< " leftover=" << leftover;
|
||||||
|
layer_leftover += leftover;
|
||||||
|
current_extruder_id = extruder_id;
|
||||||
|
}
|
||||||
|
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||||
|
if (layer_leftover > 0.f) {
|
||||||
|
total_leftover += layer_leftover;
|
||||||
|
if (layer_leftover > worst_layer_leftover) {
|
||||||
|
worst_layer_leftover = layer_leftover;
|
||||||
|
worst_layer_z = layer_tools.print_z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->throw_if_canceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_leftover > 1.f) {
|
||||||
|
this->active_step_add_warning(
|
||||||
|
PrintStateBase::WarningLevel::CRITICAL,
|
||||||
|
Slic3r::format(_u8L("The belt purge tower cannot absorb the full purge volume: %1% mm³ in total could not "
|
||||||
|
"be purged (worst layer: %2% mm³ at height %3%). The print may show color bleeding. "
|
||||||
|
"Increase the belt purge tower width, or reduce flushing volumes."),
|
||||||
|
int(std::ceil(total_leftover)), int(std::ceil(worst_layer_leftover)),
|
||||||
|
Slic3r::float_to_string_decimal_point(worst_layer_z, 2)));
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge planning leftover total=" << total_leftover
|
||||||
|
<< " worst_layer=" << worst_layer_leftover << " at print_z=" << worst_layer_z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Print::_make_wipe_tower()
|
void Print::_make_wipe_tower()
|
||||||
{
|
{
|
||||||
m_wipe_tower_data.clear();
|
m_wipe_tower_data.clear();
|
||||||
|
|||||||
@@ -558,6 +558,10 @@ private:
|
|||||||
std::vector<std::set<int>> detect_extruder_geometric_unprintables() const;
|
std::vector<std::set<int>> detect_extruder_geometric_unprintables() const;
|
||||||
|
|
||||||
void slice_volumes();
|
void slice_volumes();
|
||||||
|
// Belt mode: shift the sliced layer grid (layer print_z and the belt floor
|
||||||
|
// clipping shift) by delta, used by Print::_align_belt_purge_layers() to
|
||||||
|
// snap the purge prism's layers onto the printed objects' layer grid.
|
||||||
|
void belt_shift_layer_grid(double delta);
|
||||||
//BBS
|
//BBS
|
||||||
ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const;
|
ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const;
|
||||||
// BBS
|
// BBS
|
||||||
@@ -1085,6 +1089,8 @@ public:
|
|||||||
|
|
||||||
// Wipe tower support.
|
// Wipe tower support.
|
||||||
bool has_wipe_tower() const;
|
bool has_wipe_tower() const;
|
||||||
|
// Belt purge prism (belt-mode replacement for the wipe tower).
|
||||||
|
bool has_belt_purge_tower() const;
|
||||||
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
||||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||||
|
|
||||||
@@ -1330,6 +1336,12 @@ private:
|
|||||||
|
|
||||||
void _make_skirt();
|
void _make_skirt();
|
||||||
void _make_wipe_tower();
|
void _make_wipe_tower();
|
||||||
|
// Belt mode: route filament-change purging into the belt purge prism
|
||||||
|
// (flush-into-objects) without generating a classic wipe tower.
|
||||||
|
void _plan_belt_purge();
|
||||||
|
// Belt mode: shift the purge prism's layer grid to match the printed
|
||||||
|
// objects' grid so toolchange layers find prism layers to purge into.
|
||||||
|
void _align_belt_purge_layers();
|
||||||
void finalize_first_layer_convex_hull();
|
void finalize_first_layer_convex_hull();
|
||||||
void update_filament_self_index_cache();
|
void update_filament_self_index_cache();
|
||||||
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
||||||
|
|||||||
@@ -7321,6 +7321,20 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->set_default_value(new ConfigOptionEnum<BeltSupportZOffsetMode>(BeltSupportZOffsetMode::Unconditional));
|
def->set_default_value(new ConfigOptionEnum<BeltSupportZOffsetMode>(BeltSupportZOffsetMode::Unconditional));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def = this->add("belt_purge_tower_width", coFloat);
|
||||||
|
def->label = L("Belt purge tower width");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Width (machine X, across the belt) of the purge prism that is automatically "
|
||||||
|
"generated on belt printers when the prime tower is enabled and multiple "
|
||||||
|
"filaments are used. Filament-change purging is routed into this prism's "
|
||||||
|
"extrusions instead of a classic wipe tower. Its height is computed "
|
||||||
|
"automatically from the worst-case purge volume per layer: a wider prism "
|
||||||
|
"results in a shorter one.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = 1.;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(35.));
|
||||||
|
|
||||||
def = this->add("tree_support_branch_angle", coFloat);
|
def = this->add("tree_support_branch_angle", coFloat);
|
||||||
def->label = L("Tree support branch angle");
|
def->label = L("Tree support branch angle");
|
||||||
def->category = L("Support");
|
def->category = L("Support");
|
||||||
@@ -7966,6 +7980,16 @@ void PrintConfigDef::init_fff_params()
|
|||||||
"It will not take effect unless the prime tower is enabled.");
|
"It will not take effect unless the prime tower is enabled.");
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
// Internal marker (not shown in any settings tab): identifies the auto-generated
|
||||||
|
// belt purge prism so it can be updated/removed by the auto-manager and aligned
|
||||||
|
// to the object layer grid by the backend. Persisted to 3mf like any per-object key.
|
||||||
|
def = this->add("belt_purge_tower_object", coBool);
|
||||||
|
def->category = L("Flush options");
|
||||||
|
def->label = L("Belt purge tower object");
|
||||||
|
def->tooltip = L("Marks the auto-generated belt purge prism. Managed automatically; do not set manually.");
|
||||||
|
def->mode = comDevelop;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
def = this->add("wipe_tower_bridging", coFloat);
|
def = this->add("wipe_tower_bridging", coFloat);
|
||||||
def->label = L("Maximal bridging distance");
|
def->label = L("Maximal bridging distance");
|
||||||
def->tooltip = L("Maximal distance between supports on sparse infill sections.");
|
def->tooltip = L("Maximal distance between supports on sparse infill sections.");
|
||||||
|
|||||||
@@ -1228,6 +1228,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
// BBS
|
// BBS
|
||||||
((ConfigOptionBool, flush_into_infill))
|
((ConfigOptionBool, flush_into_infill))
|
||||||
((ConfigOptionBool, flush_into_support))
|
((ConfigOptionBool, flush_into_support))
|
||||||
|
// Marker for the auto-generated belt purge prism; identifies the object to
|
||||||
|
// the auto-manager (GUI) and the layer-grid alignment step (backend).
|
||||||
|
((ConfigOptionBool, belt_purge_tower_object))
|
||||||
// BBS
|
// BBS
|
||||||
((ConfigOptionFloat, tree_support_branch_distance))
|
((ConfigOptionFloat, tree_support_branch_distance))
|
||||||
((ConfigOptionFloat, tree_support_tip_diameter))
|
((ConfigOptionFloat, tree_support_tip_diameter))
|
||||||
@@ -1802,6 +1805,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionFloat, belt_support_floor_offset))
|
((ConfigOptionFloat, belt_support_floor_offset))
|
||||||
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
|
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
|
||||||
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
|
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
|
||||||
|
// Width (machine X, across the belt) of the auto-generated belt purge prism.
|
||||||
|
((ConfigOptionFloat, belt_purge_tower_width))
|
||||||
//BBS
|
//BBS
|
||||||
((ConfigOptionInts, additional_cooling_fan_speed))
|
((ConfigOptionInts, additional_cooling_fan_speed))
|
||||||
((ConfigOptionInts, close_additional_fan_first_x_layers))
|
((ConfigOptionInts, close_additional_fan_first_x_layers))
|
||||||
|
|||||||
@@ -1663,7 +1663,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
} else if (
|
} else if (
|
||||||
opt_key == "flush_into_infill"
|
opt_key == "flush_into_infill"
|
||||||
|| opt_key == "flush_into_objects"
|
|| opt_key == "flush_into_objects"
|
||||||
|| opt_key == "flush_into_support") {
|
|| opt_key == "flush_into_support"
|
||||||
|
|| opt_key == "belt_purge_tower_object") {
|
||||||
invalidated |= m_print->invalidate_step(psWipeTower);
|
invalidated |= m_print->invalidate_step(psWipeTower);
|
||||||
invalidated |= m_print->invalidate_step(psGCodeExport);
|
invalidated |= m_print->invalidate_step(psGCodeExport);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1392,6 +1392,27 @@ void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel thro
|
|||||||
}); // end of parallel_for
|
}); // end of parallel_for
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt mode: shift the sliced layer grid by delta. Mirrors the global_z_offset
|
||||||
|
// application in slice() — layer print_z and belt_floor_z_shift move together
|
||||||
|
// so belt floor clipping stays consistent with the shifted grid. Used by
|
||||||
|
// Print::_align_belt_purge_layers() to snap the purge prism onto the printed
|
||||||
|
// objects' layer grid; |delta| <= half a layer height, i.e. a sub-layer shift
|
||||||
|
// of the prism along the belt.
|
||||||
|
void PrintObject::belt_shift_layer_grid(double delta)
|
||||||
|
{
|
||||||
|
if (std::abs(delta) < EPSILON)
|
||||||
|
return;
|
||||||
|
for (Layer *layer : m_layers)
|
||||||
|
layer->print_z += delta;
|
||||||
|
for (SupportLayer *layer : m_support_layers)
|
||||||
|
layer->print_z += delta;
|
||||||
|
m_slicing_params.belt_floor_z_shift += delta;
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] belt_shift_layer_grid"
|
||||||
|
<< " obj=" << this->model_object()->name
|
||||||
|
<< " delta=" << delta
|
||||||
|
<< " first_layer.print_z=" << (m_layers.empty() ? 0. : m_layers.front()->print_z);
|
||||||
|
}
|
||||||
|
|
||||||
// 1) Decides Z positions of the layers,
|
// 1) Decides Z positions of the layers,
|
||||||
// 2) Initializes layers and their regions
|
// 2) Initializes layers and their regions
|
||||||
// 3) Slices the object meshes
|
// 3) Slices the object meshes
|
||||||
|
|||||||
@@ -971,11 +971,16 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
|||||||
toggle_line("preheat_steps", have_ooze_prevention && (preheat_steps > 0));
|
toggle_line("preheat_steps", have_ooze_prevention && (preheat_steps > 0));
|
||||||
|
|
||||||
bool have_prime_tower = config->opt_bool("enable_prime_tower");
|
bool have_prime_tower = config->opt_bool("enable_prime_tower");
|
||||||
|
// ORCA-Belt: belt printers replace the classic wipe tower with the
|
||||||
|
// auto-generated belt purge prism — only its width is tunable; the
|
||||||
|
// classic tower geometry options do not apply. (is_belt_printer is
|
||||||
|
// computed at the top of this function.)
|
||||||
|
toggle_line("belt_purge_tower_width", have_prime_tower && is_belt_printer);
|
||||||
for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework", "enable_tower_interface_features"})
|
for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework", "enable_tower_interface_features"})
|
||||||
toggle_line(el, have_prime_tower);
|
toggle_line(el, have_prime_tower && !is_belt_printer);
|
||||||
|
|
||||||
toggle_line("enable_tower_interface_cooldown_during_tower",
|
toggle_line("enable_tower_interface_cooldown_during_tower",
|
||||||
have_prime_tower && config->opt_bool("enable_tower_interface_features"));
|
have_prime_tower && !is_belt_printer && config->opt_bool("enable_tower_interface_features"));
|
||||||
|
|
||||||
bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower");
|
bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower");
|
||||||
|
|
||||||
@@ -983,17 +988,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
|||||||
"wipe_tower_extra_spacing", "wipe_tower_max_purge_speed",
|
"wipe_tower_extra_spacing", "wipe_tower_max_purge_speed",
|
||||||
"wipe_tower_bridging", "wipe_tower_extra_flow",
|
"wipe_tower_bridging", "wipe_tower_extra_flow",
|
||||||
"wipe_tower_no_sparse_layers"})
|
"wipe_tower_no_sparse_layers"})
|
||||||
toggle_line(el, have_prime_tower && supports_wipe_tower_2);
|
toggle_line(el, have_prime_tower && supports_wipe_tower_2 && !is_belt_printer);
|
||||||
|
|
||||||
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
|
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
|
||||||
bool have_rib_wall = (wipe_tower_wall_type == WipeTowerWallType::wtwRib)&&have_prime_tower;
|
bool have_rib_wall = (wipe_tower_wall_type == WipeTowerWallType::wtwRib)&&have_prime_tower&&!is_belt_printer;
|
||||||
toggle_line("wipe_tower_cone_angle", have_prime_tower && supports_wipe_tower_2 && wipe_tower_wall_type == WipeTowerWallType::wtwCone);
|
toggle_line("wipe_tower_cone_angle", have_prime_tower && supports_wipe_tower_2 && !is_belt_printer && wipe_tower_wall_type == WipeTowerWallType::wtwCone);
|
||||||
toggle_line("wipe_tower_extra_rib_length", have_rib_wall);
|
toggle_line("wipe_tower_extra_rib_length", have_rib_wall);
|
||||||
toggle_line("wipe_tower_rib_width", have_rib_wall);
|
toggle_line("wipe_tower_rib_width", have_rib_wall);
|
||||||
toggle_line("wipe_tower_fillet_wall", have_rib_wall);
|
toggle_line("wipe_tower_fillet_wall", have_rib_wall);
|
||||||
toggle_field("prime_tower_width", have_prime_tower && !have_rib_wall);
|
toggle_field("prime_tower_width", have_prime_tower && !have_rib_wall);
|
||||||
|
|
||||||
toggle_line("single_extruder_multi_material_priming", !bSEMM && have_prime_tower && supports_wipe_tower_2);
|
toggle_line("single_extruder_multi_material_priming", !bSEMM && have_prime_tower && supports_wipe_tower_2 && !is_belt_printer);
|
||||||
|
|
||||||
toggle_line("prime_volume",have_prime_tower && (!purge_in_primetower || !bSEMM));
|
toggle_line("prime_volume",have_prime_tower && (!purge_in_primetower || !bSEMM));
|
||||||
|
|
||||||
|
|||||||
@@ -2866,7 +2866,13 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
need_wipe_tower |= dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value;
|
need_wipe_tower |= dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
|
// Belt printers replace the classic wipe tower with the auto-generated
|
||||||
|
// belt purge prism (a real model object), so never draw the tower widget.
|
||||||
|
bool is_belt_printer = false;
|
||||||
|
if (const auto *belt_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("belt_printer"))
|
||||||
|
is_belt_printer = belt_opt->value;
|
||||||
|
|
||||||
|
if (wt && !is_belt_printer && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
|
||||||
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
||||||
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
||||||
PartPlate* part_plate = ppl.get_plate(plate_id);
|
PartPlate* part_plate = ppl.get_plate(plate_id);
|
||||||
|
|||||||
@@ -291,6 +291,12 @@ void ArrangeJob::prepare_wipe_tower()
|
|||||||
bool enable_prime_tower = op && op->getBool();
|
bool enable_prime_tower = op && op->getBool();
|
||||||
if (!enable_prime_tower || params.is_seq_print) return;
|
if (!enable_prime_tower || params.is_seq_print) return;
|
||||||
|
|
||||||
|
// Belt printers have no classic wipe tower; purging goes into the belt
|
||||||
|
// purge prism, which is a real model object and arranges like any other.
|
||||||
|
if (const auto *belt_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("belt_printer");
|
||||||
|
belt_opt && belt_opt->value)
|
||||||
|
return;
|
||||||
|
|
||||||
bool smooth_timelapse = false;
|
bool smooth_timelapse = false;
|
||||||
auto sop = current_config.option("timelapse_type");
|
auto sop = current_config.option("timelapse_type");
|
||||||
if (sop) { smooth_timelapse = sop->getInt() == TimelapseType::tlSmooth; }
|
if (sop) { smooth_timelapse = sop->getInt() == TimelapseType::tlSmooth; }
|
||||||
|
|||||||
@@ -5475,6 +5475,10 @@ struct Plater::priv
|
|||||||
void exit_gizmo();
|
void exit_gizmo();
|
||||||
void remove(size_t obj_idx);
|
void remove(size_t obj_idx);
|
||||||
bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS
|
bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS
|
||||||
|
// ORCA-Belt: keep the auto-generated belt purge prism in sync with the
|
||||||
|
// config and plate contents. Returns true when the model was mutated
|
||||||
|
// (caller should refresh the scene).
|
||||||
|
bool ensure_belt_purge_tower();
|
||||||
void delete_all_objects_from_model();
|
void delete_all_objects_from_model();
|
||||||
void reset(bool apply_presets_change = false);
|
void reset(bool apply_presets_change = false);
|
||||||
void center_selection();
|
void center_selection();
|
||||||
@@ -8879,6 +8883,225 @@ void Plater::priv::process_validation_warnings(const std::vector<StringObjectExc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ORCA-Belt: auto-managed purge prism for belt printers. The classic wipe
|
||||||
|
// tower is disabled in belt mode (its G-code bypasses the belt transform), so
|
||||||
|
// filament-change purging is routed into this prism via flush_into_objects
|
||||||
|
// (see Print::_plan_belt_purge()). The prism is a real model object so it is
|
||||||
|
// sliced through the normal pipeline and picks up the belt rotation.
|
||||||
|
//
|
||||||
|
// Width across the belt is user-set (belt_purge_tower_width); height is sized
|
||||||
|
// so each tilted slicing plane's cross-section through the prism can absorb
|
||||||
|
// the worst-case purge volume of one layer; length follows the printed
|
||||||
|
// objects along the belt (plus tilt lead-in/lead-out). Runs on every
|
||||||
|
// background-process update, so it must be idempotent: it only mutates the
|
||||||
|
// model when the desired prism differs from the existing one beyond coarse
|
||||||
|
// tolerances.
|
||||||
|
bool Plater::priv::ensure_belt_purge_tower()
|
||||||
|
{
|
||||||
|
auto is_prism = [](const ModelObject *mo) {
|
||||||
|
const ConfigOption *opt = mo->config.option("belt_purge_tower_object");
|
||||||
|
return opt != nullptr && opt->getBool();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<int> prism_idxs;
|
||||||
|
for (int i = 0; i < (int) model.objects.size(); ++i)
|
||||||
|
if (is_prism(model.objects[i]))
|
||||||
|
prism_idxs.push_back(i);
|
||||||
|
|
||||||
|
// Deletes prism objects, keeping the sidebar and part plates in sync
|
||||||
|
// (same primitives as Plater::priv::remove(), minus scene update — the
|
||||||
|
// caller refreshes the scene).
|
||||||
|
auto remove_prisms = [this](const std::vector<int> &idxs) {
|
||||||
|
for (auto it = idxs.rbegin(); it != idxs.rend(); ++it) {
|
||||||
|
model.delete_object(size_t(*it));
|
||||||
|
partplate_list.notify_instance_removed(*it, -1);
|
||||||
|
sidebar->obj_list()->delete_object_from_list(size_t(*it));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto &printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
|
const auto &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
|
auto &project_config = wxGetApp().preset_bundle->project_config;
|
||||||
|
|
||||||
|
const auto *belt_opt = printer_config.option<ConfigOptionBool>("belt_printer");
|
||||||
|
const bool belt = belt_opt != nullptr && belt_opt->value;
|
||||||
|
const bool prime_tower_enabled = print_config.has("enable_prime_tower") && print_config.opt_bool("enable_prime_tower");
|
||||||
|
const auto *seq_opt = print_config.option<ConfigOptionEnum<PrintSequence>>("print_sequence");
|
||||||
|
const bool by_object = seq_opt != nullptr && seq_opt->value == PrintSequence::ByObject;
|
||||||
|
|
||||||
|
// Filaments used and bounding extent of the non-prism objects on the
|
||||||
|
// current plate (1-based filament ids; volume extruder 0 = object default).
|
||||||
|
PartPlate *plate = partplate_list.get_curr_plate();
|
||||||
|
std::set<int> filaments;
|
||||||
|
double y_min = std::numeric_limits<double>::max();
|
||||||
|
double y_max = -std::numeric_limits<double>::max();
|
||||||
|
double z_max = 0.;
|
||||||
|
bool have_objects = false;
|
||||||
|
if (belt && plate != nullptr) {
|
||||||
|
for (int obj_idx = 0; obj_idx < (int) model.objects.size(); ++obj_idx) {
|
||||||
|
const ModelObject *mo = model.objects[obj_idx];
|
||||||
|
if (is_prism(mo))
|
||||||
|
continue;
|
||||||
|
int obj_extruder = 1;
|
||||||
|
if (const ConfigOption *opt = mo->config.option("extruder"); opt != nullptr && opt->getInt() > 0)
|
||||||
|
obj_extruder = opt->getInt();
|
||||||
|
bool any_instance_on_plate = false;
|
||||||
|
for (int inst_idx = 0; inst_idx < (int) mo->instances.size(); ++inst_idx) {
|
||||||
|
if (!plate->contain_instance_totally(obj_idx, inst_idx))
|
||||||
|
continue;
|
||||||
|
any_instance_on_plate = true;
|
||||||
|
const BoundingBoxf3 bb = mo->instance_bounding_box(inst_idx);
|
||||||
|
y_min = std::min(y_min, bb.min.y());
|
||||||
|
y_max = std::max(y_max, bb.max.y());
|
||||||
|
z_max = std::max(z_max, bb.max.z());
|
||||||
|
}
|
||||||
|
if (!any_instance_on_plate)
|
||||||
|
continue;
|
||||||
|
have_objects = true;
|
||||||
|
for (const ModelVolume *mv : mo->volumes)
|
||||||
|
for (int e : mv->get_extruders())
|
||||||
|
filaments.insert(e > 0 ? e : obj_extruder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wanted = belt && prime_tower_enabled && !by_object && have_objects && filaments.size() > 1;
|
||||||
|
if (!wanted) {
|
||||||
|
if (prism_idxs.empty())
|
||||||
|
return false;
|
||||||
|
remove_prisms(prism_idxs);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "[BELT-DEBUG] belt purge tower removed (conditions not met)";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sizing -----------------------------------------------------------
|
||||||
|
const double width = print_config.has("belt_purge_tower_width") ? std::max(1., print_config.opt_float("belt_purge_tower_width")) : 35.;
|
||||||
|
const double layer_h = print_config.has("layer_height") ? print_config.opt_float("layer_height") : 0.2;
|
||||||
|
|
||||||
|
// Belt tilt: standard belt printers rotate about X. For other axes (or no
|
||||||
|
// rotation) fall back to vertical slicing geometry (theta = 90 deg); the
|
||||||
|
// backend leftover warning catches any resulting under-absorption.
|
||||||
|
double theta = M_PI / 2.;
|
||||||
|
const auto *axis_opt = printer_config.option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation");
|
||||||
|
const auto *angle_opt = printer_config.option<ConfigOptionFloat>("belt_slice_rotation_angle");
|
||||||
|
if (axis_opt != nullptr && axis_opt->value == BeltRotationAxis::X && angle_opt != nullptr && std::abs(angle_opt->value) > EPSILON)
|
||||||
|
theta = std::clamp(Geometry::deg2rad(std::abs(angle_opt->value)), Geometry::deg2rad(5.), M_PI / 2.);
|
||||||
|
const double sin_t = std::sin(theta);
|
||||||
|
const double cot_t = std::cos(theta) / sin_t;
|
||||||
|
|
||||||
|
// Worst-case purge volume of one layer: up to (filament count - 1)
|
||||||
|
// toolchanges, each needing the worst flush matrix entry (mirrors the
|
||||||
|
// volume selection in Print::_plan_belt_purge()).
|
||||||
|
const bool use_matrix = (print_config.has("purge_in_prime_tower") && print_config.opt_bool("purge_in_prime_tower"))
|
||||||
|
&& (printer_config.has("single_extruder_multi_material") && printer_config.opt_bool("single_extruder_multi_material"));
|
||||||
|
double max_flush = print_config.has("prime_volume") ? print_config.opt_float("prime_volume") : 45.;
|
||||||
|
if (use_matrix) {
|
||||||
|
const size_t extruder_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||||
|
const std::vector<double> matrix = get_flush_volumes_matrix(
|
||||||
|
project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values, 0, extruder_nums);
|
||||||
|
const auto * multi_opt = project_config.option<ConfigOptionFloats>("flush_multiplier");
|
||||||
|
const double multiplier = multi_opt != nullptr && !multi_opt->values.empty() ? multi_opt->get_at(0) : 1.;
|
||||||
|
const int n_total = (int) (std::sqrt(double(matrix.size())) + 0.5);
|
||||||
|
double m = 0.;
|
||||||
|
for (int i : filaments)
|
||||||
|
for (int j : filaments)
|
||||||
|
if (i != j && i <= n_total && j <= n_total)
|
||||||
|
m = std::max(m, matrix[size_t(i - 1) * n_total + size_t(j - 1)]);
|
||||||
|
if (m > 0.)
|
||||||
|
max_flush = m * multiplier;
|
||||||
|
}
|
||||||
|
const double v_layer = double(filaments.size() - 1) * max_flush;
|
||||||
|
|
||||||
|
// One tilted slicing plane cuts a width x (height/sin) rectangle out of
|
||||||
|
// the prism interior; one layer slab absorbs that area x layer height.
|
||||||
|
const double eta = 0.85; // perimeters/infill packing safety factor
|
||||||
|
double height = v_layer * sin_t / (width * layer_h * eta);
|
||||||
|
const double printable_height = printer_config.has("printable_height") ? printer_config.opt_float("printable_height") : 250.;
|
||||||
|
height = std::clamp(height, 2. * layer_h, std::max(2. * layer_h, printable_height));
|
||||||
|
|
||||||
|
// Length along the belt: cover the objects' Y extent; a slicing plane
|
||||||
|
// spans height*cot(theta) of belt travel, so pad both ends with the full
|
||||||
|
// lead-in (tilt sign is padded symmetrically) plus the objects' top
|
||||||
|
// overhang along the belt.
|
||||||
|
const double margin = 10.;
|
||||||
|
const double lead = height * cot_t;
|
||||||
|
double y_start = y_min - lead - margin;
|
||||||
|
double y_end = y_max + (z_max + height) * cot_t + margin;
|
||||||
|
|
||||||
|
const Vec3d plate_origin = plate->get_origin();
|
||||||
|
const auto *bed_opt = printer_config.option<ConfigOptionPoints>("printable_area");
|
||||||
|
const bool infinite_y = printer_config.has("belt_printer_infinite_y") && printer_config.opt_bool("belt_printer_infinite_y");
|
||||||
|
double lane_x = plate_origin.x() + 5.; // fallback left lane
|
||||||
|
if (bed_opt != nullptr && !bed_opt->values.empty()) {
|
||||||
|
const BoundingBoxf bed_ext = get_extents(bed_opt->values);
|
||||||
|
lane_x = plate_origin.x() + bed_ext.min.x() + 5.;
|
||||||
|
if (!infinite_y) {
|
||||||
|
y_start = std::max(y_start, plate_origin.y() + bed_ext.min.y() + 1.);
|
||||||
|
y_end = std::min(y_end, plate_origin.y() + bed_ext.max.y() - 1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double length = std::max(y_end - y_start, 10.);
|
||||||
|
|
||||||
|
const Vec3d desired_size(width, length, height);
|
||||||
|
const Vec3d desired_center(lane_x + 0.5 * width, y_start + 0.5 * length, 0.5 * height);
|
||||||
|
|
||||||
|
// --- Idempotence check -------------------------------------------------
|
||||||
|
// Coarse tolerances so object nudges and sizing jitter do not regenerate
|
||||||
|
// the prism on every background-process tick.
|
||||||
|
if (prism_idxs.size() == 1) {
|
||||||
|
const ModelObject *prism = model.objects[prism_idxs.front()];
|
||||||
|
const BoundingBoxf3 bb = prism->bounding_box_exact();
|
||||||
|
if ((bb.size() - desired_size).cwiseAbs().maxCoeff() < 5. && (bb.center() - desired_center).cwiseAbs().maxCoeff() < 5.)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- (Re)create ---------------------------------------------------------
|
||||||
|
if (!prism_idxs.empty())
|
||||||
|
remove_prisms(prism_idxs);
|
||||||
|
|
||||||
|
ModelObject *new_object = model.add_object();
|
||||||
|
new_object->name = _u8L("Belt Purge Tower");
|
||||||
|
new_object->add_instance();
|
||||||
|
ModelVolume *new_volume = new_object->add_volume(make_cube(width, length, height));
|
||||||
|
new_volume->name = new_object->name;
|
||||||
|
|
||||||
|
auto &cfg = new_object->config;
|
||||||
|
cfg.set_key_value("belt_purge_tower_object", new ConfigOptionBool(true));
|
||||||
|
cfg.set_key_value("flush_into_objects", new ConfigOptionBool(true));
|
||||||
|
cfg.set_key_value("extruder", new ConfigOptionInt(1));
|
||||||
|
// Sacrificial solid prism: one wall, no shells, dense rectilinear infill —
|
||||||
|
// every extrusion is overriddable, so the absorbed volume matches the
|
||||||
|
// cross-section x layer-height estimate used for the height above.
|
||||||
|
cfg.set_key_value("wall_loops", new ConfigOptionInt(1));
|
||||||
|
cfg.set_key_value("top_shell_layers", new ConfigOptionInt(0));
|
||||||
|
cfg.set_key_value("bottom_shell_layers", new ConfigOptionInt(0));
|
||||||
|
cfg.set_key_value("sparse_infill_density", new ConfigOptionPercent(100));
|
||||||
|
cfg.set_key_value("sparse_infill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||||
|
cfg.set_key_value("enable_support", new ConfigOptionBool(false));
|
||||||
|
cfg.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btNoBrim));
|
||||||
|
cfg.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None));
|
||||||
|
cfg.set_key_value("precise_z_height", new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
new_object->invalidate_bounding_box();
|
||||||
|
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
|
||||||
|
new_object->translate(-mesh_bb.center());
|
||||||
|
new_object->instances.front()->set_offset(desired_center);
|
||||||
|
new_object->ensure_on_bed();
|
||||||
|
new_object->instances.front()->set_assemble_transformation(new_object->instances.front()->get_transformation());
|
||||||
|
|
||||||
|
const size_t obj_idx = model.objects.size() - 1;
|
||||||
|
// Registers the object in the sidebar and notifies the part plates;
|
||||||
|
// selection is left untouched (auto-managed object).
|
||||||
|
sidebar->obj_list()->add_object_to_list(obj_idx, /*call_selection_changed=*/false);
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "[BELT-DEBUG] belt purge tower generated"
|
||||||
|
<< " W=" << width << " L=" << length << " H=" << height
|
||||||
|
<< " v_layer=" << v_layer << " max_flush=" << max_flush
|
||||||
|
<< " filaments=" << filaments.size()
|
||||||
|
<< " theta_deg=" << Geometry::rad2deg(theta)
|
||||||
|
<< " center=(" << desired_center.x() << "," << desired_center.y() << ")";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Update background processing thread from the current config and Model.
|
// Update background processing thread from the current config and Model.
|
||||||
// Returns a bitmask of UpdateBackgroundProcessReturnState.
|
// Returns a bitmask of UpdateBackgroundProcessReturnState.
|
||||||
unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages, bool switch_print)
|
unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages, bool switch_print)
|
||||||
@@ -8889,6 +9112,10 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
|||||||
// If the update_background_process() was not called by the timer, kill the timer,
|
// If the update_background_process() was not called by the timer, kill the timer,
|
||||||
// so the update_restart_background_process() will not be called again in vain.
|
// so the update_restart_background_process() will not be called again in vain.
|
||||||
background_process_timer.Stop();
|
background_process_timer.Stop();
|
||||||
|
// ORCA-Belt: sync the auto-managed belt purge prism before the model is
|
||||||
|
// applied to the Print below, so the prism change rides this same apply.
|
||||||
|
if (printer_technology == ptFFF && this->ensure_belt_purge_tower())
|
||||||
|
return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE;
|
||||||
// Update the "out of print bed" state of ModelInstances.
|
// Update the "out of print bed" state of ModelInstances.
|
||||||
update_print_volume_state();
|
update_print_volume_state();
|
||||||
// Apply new config to the possibly running background task.
|
// Apply new config to the possibly running background task.
|
||||||
|
|||||||
@@ -3009,6 +3009,7 @@ void TabPrint::build()
|
|||||||
optgroup->append_single_option_line("enable_tower_interface_cooldown_during_tower", "multimaterial_settings_prime_tower");
|
optgroup->append_single_option_line("enable_tower_interface_cooldown_during_tower", "multimaterial_settings_prime_tower");
|
||||||
optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower");
|
optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower");
|
||||||
optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width");
|
optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width");
|
||||||
|
optgroup->append_single_option_line("belt_purge_tower_width", "multimaterial_settings_prime_tower");
|
||||||
optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower");
|
optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower");
|
||||||
optgroup->append_single_option_line("prime_tower_brim_width", "multimaterial_settings_prime_tower#brim-width");
|
optgroup->append_single_option_line("prime_tower_brim_width", "multimaterial_settings_prime_tower#brim-width");
|
||||||
optgroup->append_single_option_line("prime_tower_infill_gap", "multimaterial_settings_prime_tower");
|
optgroup->append_single_option_line("prime_tower_infill_gap", "multimaterial_settings_prime_tower");
|
||||||
|
|||||||
Reference in New Issue
Block a user