mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-13 02:41:16 +00:00
Merge upstream main: belt printing, texture displacement color mixing, 3MF component cycle checks, undo blocked during background jobs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+314
-50
@@ -71,6 +71,8 @@
|
||||
#include "Thread.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "BeltGCode.hpp"
|
||||
#include "BeltTransform.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "GCode/WipeTowerEstimate.hpp"
|
||||
@@ -79,6 +81,7 @@
|
||||
#include "MaterialType.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "format.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include <float.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -105,6 +108,7 @@
|
||||
#include "Format/STEP.hpp"
|
||||
#include "PlaceholderParser.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "BeltBrim.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@@ -168,6 +172,14 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
// Cache the plenty of parameters, which influence the G-code generator only,
|
||||
// or they are only notes not influencing the generated G-code.
|
||||
static std::unordered_set<std::string> steps_gcode = {
|
||||
// Belt printer G-code axis remap (only affects G-code output, not slicing).
|
||||
"gcode_remap_x",
|
||||
"gcode_remap_y",
|
||||
"gcode_remap_z",
|
||||
// Machine-frame transform (derived from belt tilt; only affects G-code output).
|
||||
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
|
||||
// Only inflates the GUI bed volume, like printable_area.
|
||||
"belt_printer_infinite_y",
|
||||
//BBS
|
||||
"additional_cooling_fan_speed",
|
||||
"reduce_crossing_wall",
|
||||
@@ -369,8 +381,18 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
// Spiral Vase forces different kind of slicing than the normal model:
|
||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
||||
|| opt_key == "spiral_mode") {
|
||||
|| opt_key == "spiral_mode"
|
||||
// Build plate tilt changes slicing plane orientation.
|
||||
|| opt_key == "build_plate_tilt_x"
|
||||
|| opt_key == "build_plate_tilt_y"
|
||||
// Belt printer transform options change the mesh geometry before slicing.
|
||||
|| opt_key == "belt_printer"
|
||||
|| opt_key == "belt_slice_rotation"
|
||||
|| opt_key == "belt_slice_rotation_angle") {
|
||||
osteps.emplace_back(posSlice);
|
||||
} else if (
|
||||
opt_key == "belt_support_floor_offset") {
|
||||
osteps.emplace_back(posSupportMaterial);
|
||||
} else if (
|
||||
opt_key == "print_sequence"
|
||||
|| opt_key == "filament_type"
|
||||
@@ -405,6 +427,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "hot_plate_temp"
|
||||
|| opt_key == "textured_plate_temp"
|
||||
|| opt_key == "enable_prime_tower"
|
||||
|| opt_key == "enable_belt_purge_tower"
|
||||
|| opt_key == "enable_wrapping_detection"
|
||||
|| opt_key == "prime_tower_enable_framework"
|
||||
|| opt_key == "prime_tower_width"
|
||||
@@ -439,6 +462,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "prime_volume"
|
||||
|| opt_key == "flush_into_infill"
|
||||
|| opt_key == "flush_into_support"
|
||||
|| opt_key == "belt_purge_tower_width"
|
||||
|| opt_key == "initial_layer_infill_speed"
|
||||
|| opt_key == "travel_speed"
|
||||
|| opt_key == "travel_speed_z"
|
||||
@@ -676,6 +700,9 @@ std::vector<ObjectID> Print::print_object_ids() const
|
||||
|
||||
bool Print::has_infinite_skirt() const
|
||||
{
|
||||
// Belt printer: no skirt support.
|
||||
if (m_config.belt_printer.value)
|
||||
return false;
|
||||
// Orca: unclear why (m_config.ooze_prevention && this->extruders().size() > 1) logic is here, removed.
|
||||
// return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
||||
|
||||
@@ -684,6 +711,9 @@ bool Print::has_infinite_skirt() const
|
||||
|
||||
bool Print::has_skirt() const
|
||||
{
|
||||
// Belt printer: no skirt support.
|
||||
if (m_config.belt_printer.value)
|
||||
return false;
|
||||
return (m_config.skirt_height > 0);
|
||||
}
|
||||
|
||||
@@ -692,6 +722,24 @@ bool Print::has_brim() const
|
||||
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_brim(); });
|
||||
}
|
||||
|
||||
bool Print::has_tilted_belt() const
|
||||
{
|
||||
if (! m_config.belt_printer.value)
|
||||
return false;
|
||||
// A Z rotation leaves the belt floor flat (BeltTransform forces shear = 0) and no
|
||||
// rotation at all means the machine is geometrically a flat bed.
|
||||
const BeltRotationAxis axis = m_config.belt_slice_rotation.value;
|
||||
if (axis != BeltRotationAxis::X && axis != BeltRotationAxis::Y)
|
||||
return false;
|
||||
const double tilt = std::abs(m_config.belt_slice_rotation_angle.value);
|
||||
return tilt >= BELT_BRIM_MIN_TILT_DEG && tilt <= BELT_BRIM_MAX_TILT_DEG;
|
||||
}
|
||||
|
||||
bool Print::has_belt_brim() const
|
||||
{
|
||||
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_belt_brim(); });
|
||||
}
|
||||
|
||||
//BBS
|
||||
std::vector<size_t> Print::layers_sorted_for_object(float start, float end, std::vector<LayerPtrs> &layers_of_objects, std::vector<BoundingBox> &boundingBox_for_objects, VecOfPoints &objects_instances_shift)
|
||||
{
|
||||
@@ -1937,6 +1985,75 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
}
|
||||
}
|
||||
|
||||
// Belt printer validation: incompatible features.
|
||||
if (m_config.belt_printer.value) {
|
||||
for (const PrintObject *object : m_objects) {
|
||||
if (object->config().raft_layers > 0)
|
||||
return { L("Raft is not compatible with belt printer mode.") };
|
||||
}
|
||||
if (m_config.draft_shield != dsDisabled)
|
||||
return { L("Draft shield is not compatible with belt printer mode.") };
|
||||
|
||||
// Belt brim spans many layers and owns the layers below the object, which
|
||||
// spiral vase cannot share. The prime tower setting is no obstacle: belt
|
||||
// printers never print the classic tower, and the belt purge prism is an
|
||||
// ordinary object that never takes a brim.
|
||||
if (this->has_belt_brim()) {
|
||||
if (m_config.spiral_mode.value)
|
||||
return { L("Brim is not compatible with spiral vase mode on a belt printer. "
|
||||
"Disable one of them.") };
|
||||
}
|
||||
|
||||
for (const PrintObject *object : m_objects) {
|
||||
const PrintObjectConfig &ocfg = object->config();
|
||||
// Mirror PrintObject::has_belt_brim(): an inner-only brim needs a positive
|
||||
// brim_width (leading/extra widen only the outer ring), so keep this
|
||||
// predicate in step or the belt-brim warnings below would fire for a brim
|
||||
// that has_belt_brim() rejects.
|
||||
const bool wants_brim = ocfg.brim_type != btNoBrim
|
||||
&& (ocfg.brim_type == btInnerOnly
|
||||
? ocfg.brim_width.value > 0.
|
||||
: (ocfg.brim_width.value > 0. || ocfg.leading_brim_length.value > 0.
|
||||
|| ocfg.extra_brim_width.value > 0.));
|
||||
if (! wants_brim)
|
||||
continue;
|
||||
|
||||
if (! this->has_tilted_belt()) {
|
||||
if (std::abs(m_config.belt_slice_rotation_angle.value) > BELT_BRIM_MAX_TILT_DEG)
|
||||
warn(L("The belt is too steep for a brim, so no brim will be generated."),
|
||||
"brim_width", object->model_object());
|
||||
else
|
||||
warn(L("A brim is only generated when the belt is tilted. Set a belt tilt angle, "
|
||||
"or remove the brim setting."),
|
||||
"brim_type", object->model_object());
|
||||
}
|
||||
|
||||
if (ocfg.brim_type == btAutoBrim || ocfg.brim_type == btEar || ocfg.brim_type == btPainted)
|
||||
warn(L("Belt printers support outer and inner brim only. Auto, Mouse ear and Painted "
|
||||
"brim are printed as Outer brim only, using Brim width."),
|
||||
"brim_type", object->model_object());
|
||||
|
||||
if (ocfg.leading_brim_length.value > 0. && ocfg.brim_object_gap.value > 0.)
|
||||
warn(L("Brim-object gap separates the leading brim from the object's leading edge, "
|
||||
"which is the edge it is meant to anchor. Set the gap to 0 when using leading "
|
||||
"brim length."),
|
||||
"brim_object_gap", object->model_object());
|
||||
}
|
||||
if (this->has_belt_brim() && m_objects.size() > 1)
|
||||
warn(L("Leading brim length extends ahead of each object along the belt, and Arrange does "
|
||||
"not reserve that space. Leave room between objects."),
|
||||
"leading_brim_length");
|
||||
} else {
|
||||
// "Leading edge only" describes where a part meets a moving belt, so it has no
|
||||
// meaning on a fixed bed. Brim.cpp prints it as an ordinary outer brim rather
|
||||
// than silently producing nothing; say so.
|
||||
for (const PrintObject *object : m_objects)
|
||||
if (object->config().brim_type == btLeadingEdgeOnly)
|
||||
warn(L("\"Leading edge only\" brim applies to belt printers. On this printer it is "
|
||||
"printed as an ordinary outer brim."),
|
||||
"brim_type", object->model_object());
|
||||
}
|
||||
|
||||
// Orca: a gradient mixed filament only renders its gradient with "Mixed color sublayer" on;
|
||||
// without it ToolOrdering::resolve_mixed_filaments prints one whole component per layer and
|
||||
// the gradient is dropped silently. extruders() already covers painting, height ranges,
|
||||
@@ -2002,6 +2119,40 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
}
|
||||
}
|
||||
|
||||
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
|
||||
&& m_config.print_sequence == PrintSequence::ByObject
|
||||
&& extruders.size() > 1) {
|
||||
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_belt_purge_tower";
|
||||
warningtemp.is_warning = true;
|
||||
add_warning(warningtemp);
|
||||
}
|
||||
|
||||
// The purge tower is a model object the GUI creates and sizes; libslic3r only purges
|
||||
// into one that exists. A project sliced without it (the CLI on a project saved before
|
||||
// the tower was generated) changes filament with nowhere to purge.
|
||||
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
|
||||
&& m_config.print_sequence != PrintSequence::ByObject
|
||||
&& ! m_config.spiral_mode.value && this->object_extruders().size() > 1 && ! this->has_belt_purge_tower()) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("The belt purge tower is enabled but the project has no purge tower object; "
|
||||
"filament changes will not be purged. Open the project in the application "
|
||||
"to generate the tower.");
|
||||
warningtemp.opt_key = "enable_belt_purge_tower";
|
||||
warningtemp.is_warning = true;
|
||||
add_warning(warningtemp);
|
||||
}
|
||||
|
||||
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value) {
|
||||
const size_t prism_count = std::count_if(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
|
||||
return object->config().belt_purge_tower_object.value;
|
||||
});
|
||||
if (prism_count > 1)
|
||||
return {L("The project contains multiple managed belt purge towers. Reload the plate or toggle the belt purge tower off and on to regenerate it.")};
|
||||
}
|
||||
|
||||
if (m_config.enable_prime_tower) {
|
||||
for (const PrintObject* object : m_objects) {
|
||||
if (object->config().precise_z_height.value) {
|
||||
@@ -2055,35 +2206,57 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
return profile;
|
||||
};
|
||||
|
||||
// Checks that the print does not exceed the max print height
|
||||
// Checks that the print does not exceed the max print height.
|
||||
// For belt printers the slicing-frame Z spans the sheared X-length and
|
||||
// is not comparable to printable_height (which is gantry clearance in the
|
||||
// build-volume frame). Compare against the model's pre-shear Z instead,
|
||||
// mirroring the bbox computed in PrintObject::update_slicing_parameters.
|
||||
// The machine-frame transform only changes how that height is written to
|
||||
// G-code, not how much room there is under the gantry.
|
||||
const bool belt_printer = this->config().belt_printer.value;
|
||||
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
|
||||
for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++ print_object_idx) {
|
||||
const PrintObject &print_object = *m_objects[print_object_idx];
|
||||
//FIXME It is quite expensive to generate object layers just to get the print height!
|
||||
if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
|
||||
!layers.empty()) {
|
||||
|
||||
Vec3d test =this->shrinkage_compensation();
|
||||
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
|
||||
|
||||
if (shrinkage_compensation_z != 1. && layers.back() > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
||||
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
||||
return StringObjectException{
|
||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name),
|
||||
print_object.model_object(),
|
||||
""
|
||||
};
|
||||
} else if (layers.back() > this->config().printable_height + EPSILON) {
|
||||
// Test whether the last slicing plane is below or above the print volume.
|
||||
return StringObjectException{
|
||||
0.5 * (layers[layers.size() - 2] + layers.back()) > this->config().printable_height + EPSILON ?
|
||||
Slic3r::format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name) :
|
||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) +
|
||||
" " + _u8L("You might want to reduce the size of your model or change current print settings and retry."),
|
||||
print_object.model_object(),
|
||||
""
|
||||
};
|
||||
double effective_max_z = 0;
|
||||
bool last_layer_below_max = false;
|
||||
bool have_height = false;
|
||||
|
||||
if (belt_printer) {
|
||||
const double raw_z = print_object.model_object()->max_z();
|
||||
effective_max_z = raw_z;
|
||||
have_height = raw_z > 0;
|
||||
} else {
|
||||
//FIXME It is quite expensive to generate object layers just to get the print height!
|
||||
auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
|
||||
if (!layers.empty()) {
|
||||
effective_max_z = layers.back();
|
||||
last_layer_below_max = layers.size() >= 2 &&
|
||||
0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
||||
have_height = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_height)
|
||||
continue;
|
||||
|
||||
if (shrinkage_compensation_z != 1. && effective_max_z > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
||||
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
||||
return StringObjectException{
|
||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name),
|
||||
print_object.model_object(),
|
||||
""
|
||||
};
|
||||
} else if (effective_max_z > this->config().printable_height + EPSILON) {
|
||||
return StringObjectException{
|
||||
last_layer_below_max ?
|
||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) +
|
||||
" " + _u8L("You might want to reduce the size of your model or change current print settings and retry.") :
|
||||
Slic3r::format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name),
|
||||
print_object.model_object(),
|
||||
""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Some of the objects has variable layer height applied by painting or by a table.
|
||||
@@ -2103,12 +2276,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
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()) {
|
||||
// Orca: wipe_tower_filament (issue #10971) is inserted into the tool order after
|
||||
// resolve_mixed_filaments has expanded every mixed (virtual) slot, so a mixed slot here
|
||||
// would reach the G-code as a tool change to a slot no nozzle carries. The GUI hides
|
||||
// mixed slots from the option; this guards loaded projects and the CLI.
|
||||
if (m_config.wipe_tower_filament > 0) {
|
||||
if (this->has_wipe_tower() && m_config.wipe_tower_filament > 0) {
|
||||
const auto &is_mixed = m_config.filament_is_mixed.values;
|
||||
const size_t wipe_idx = size_t(m_config.wipe_tower_filament - 1);
|
||||
if (wipe_idx < is_mixed.size() && is_mixed[wipe_idx])
|
||||
@@ -2130,12 +2303,17 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
}
|
||||
}
|
||||
|
||||
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).") };
|
||||
// The following two constraints come from the classic wipe tower G-code
|
||||
// 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 (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)
|
||||
@@ -2862,8 +3040,28 @@ BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std
|
||||
a += area(slice);
|
||||
}
|
||||
}
|
||||
if (has_brim())
|
||||
// Guard on `defined`: make_brim() can return before assigning this (it does on
|
||||
// belt printers, where has_brim() is still true but the plate brim is skipped),
|
||||
// and overwriting a valid bbox with an undefined one corrupted the first-layer
|
||||
// centre and the GUI's first-layer area readout.
|
||||
if (has_brim() && firstLayerObjectBrimBoundingBox.defined)
|
||||
bbox = firstLayerObjectBrimBoundingBox;
|
||||
// Belt brim: the apron reaches ahead of the object along the belt.
|
||||
if (has_belt_brim()) {
|
||||
const Point shift = instances().empty() ? Point(0, 0) : instances()[0].shift_without_plate_offset();
|
||||
for (const ExPolygons &areas : m_belt_brim_areas_by_layer)
|
||||
for (const ExPolygon &ex : areas) {
|
||||
BoundingBox bb = get_extents(ex.contour);
|
||||
bb.translate(shift.x(), shift.y());
|
||||
bbox.merge(bb);
|
||||
}
|
||||
for (const BeltBrimBand &band : m_belt_brim_prologue)
|
||||
for (const ExPolygon &ex : band.areas) {
|
||||
BoundingBox bb = get_extents(ex.contour);
|
||||
bb.translate(shift.x(), shift.y());
|
||||
bbox.merge(bb);
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
@@ -2914,6 +3112,19 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
if (m_objects.empty())
|
||||
return;
|
||||
|
||||
// Belt purge prism: _plan_belt_purge() (psWipeTower) truncates the prism's
|
||||
// layers and drops its unclaimed fills, stashing both so a replan can undo
|
||||
// them. The object steps below regenerate per-layer content over m_layers
|
||||
// ONLY, so if any of them is about to rerun the stashes must go back first;
|
||||
// otherwise truncated layers keep stale perimeters/fills and dropped fills
|
||||
// are re-inserted next to freshly generated ones. Every object-step
|
||||
// invalidation also invalidates psWipeTower, so "psWipeTower not done" is
|
||||
// exactly "some object step may rerun" -- and when it IS done nothing below
|
||||
// regenerates, and the plan's edits have to stay.
|
||||
if (!this->is_step_done(psWipeTower))
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->belt_undo_purge_plan();
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
@@ -2975,15 +3186,20 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
int object_count = m_objects.size();
|
||||
std::set<PrintObject*> need_slicing_objects;
|
||||
std::set<PrintObject*> re_slicing_objects;
|
||||
// Belt global modes couple each object's bed position into its layer Z values,
|
||||
// so sharing layers between "identical" objects is wrong.
|
||||
bool belt_no_share = m_config.belt_printer.value;
|
||||
if (!use_cache) {
|
||||
for (int index = 0; index < object_count; index++)
|
||||
{
|
||||
PrintObject *obj = m_objects[index];
|
||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||
{
|
||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||
obj->set_shared_object(slicing_obj);
|
||||
break;
|
||||
if (!belt_no_share) {
|
||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||
{
|
||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||
obj->set_shared_object(slicing_obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!obj->get_shared_object())
|
||||
@@ -3002,12 +3218,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
PrintObject *obj = m_objects[index];
|
||||
bool found_shared = false;
|
||||
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
|
||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||
{
|
||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||
obj->set_shared_object(slicing_obj);
|
||||
found_shared = true;
|
||||
break;
|
||||
if (!belt_no_share) {
|
||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||
{
|
||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||
obj->set_shared_object(slicing_obj);
|
||||
found_shared = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found_shared) {
|
||||
@@ -3130,7 +3348,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
for (int i = range.begin(); i < range.end(); i++) {
|
||||
PrintObject* obj = m_objects[i];
|
||||
if (need_slicing_objects.count(obj) != 0) {
|
||||
obj->generate_support_material();
|
||||
// The belt brim follows sequentially below.
|
||||
obj->generate_support_material(false);
|
||||
}
|
||||
else {
|
||||
if (obj->set_started(posSupportMaterial))
|
||||
@@ -3139,6 +3358,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
}
|
||||
}
|
||||
);
|
||||
// The belt brim keeps clear of every object's layers and support layers,
|
||||
// so it runs once no support step is rebuilding them any more.
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->generate_belt_brim();
|
||||
|
||||
if (m_pipeline_plugin_active)
|
||||
for (size_t i = 0; i < m_objects.size(); ++i)
|
||||
@@ -3215,7 +3438,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
m_wipe_tower_data.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();
|
||||
}
|
||||
else if (this->config().print_sequence != PrintSequence::ByObject) {
|
||||
@@ -3469,6 +3695,26 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
}
|
||||
|
||||
|
||||
// Belt brim: bound the first-layer convex hull by the lowest apron band, so
|
||||
// bed levelling and the initial purge line account for brim that reaches
|
||||
// ahead of every object.
|
||||
if (this->has_belt_brim()) {
|
||||
for (PrintObject *object : m_objects) {
|
||||
if (! object->has_belt_brim() || object->belt_brim_prologue().empty())
|
||||
continue;
|
||||
const BeltBrimBand &lowest = object->belt_brim_prologue().front();
|
||||
for (const PrintInstance &instance : object->instances())
|
||||
for (const ExPolygon &ex : lowest.areas) {
|
||||
Polygon poly = ex.contour;
|
||||
poly.translate(instance.shift);
|
||||
append(m_first_layer_convex_hull.points, std::move(poly.points));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unchanged for belt printers: _make_skirt() already returns early for them, and
|
||||
// the belt brim does not populate m_brimMapByInstance, which is what the
|
||||
// skirt/brim grouping reads.
|
||||
if (has_skirt() || has_infinite_skirt() || has_brim()) {
|
||||
// Generate skirt/brim groups after brim so per-object and draft-shield footprints
|
||||
// include brims when grouping and offsetting skirt loops.
|
||||
@@ -3572,7 +3818,12 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
this->set_status(80, message);
|
||||
|
||||
// The following line may die for multiple reasons.
|
||||
GCode gcode;
|
||||
// Factory: use BeltGCode for belt printers, plain GCode otherwise.
|
||||
std::unique_ptr<GCode> gcode;
|
||||
if (m_config.belt_printer.value)
|
||||
gcode = std::make_unique<BeltGCode>();
|
||||
else
|
||||
gcode = std::make_unique<GCode>();
|
||||
//BBS: compute plate offset for gcode-generator
|
||||
const Vec3d origin = this->get_plate_origin();
|
||||
// IMEX firmware-managed zones: writer offset gets the IMEX shift so emitted gcode is
|
||||
@@ -3584,9 +3835,9 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
// still emits in the frame the current config asks for.
|
||||
this->update_imex_slice_offset();
|
||||
const Vec2d imex_off = this->get_imex_slice_offset();
|
||||
gcode.set_gcode_offset_with_imex_shift(origin(0), origin(1), imex_off.x(), imex_off.y());
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode.export_layer_filaments(result);
|
||||
gcode->set_gcode_offset_with_imex_shift(origin(0), origin(1), imex_off.x(), imex_off.y());
|
||||
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode->export_layer_filaments(result);
|
||||
//BBS
|
||||
if (result != nullptr) {
|
||||
result->conflict_result = m_conflict_result;
|
||||
@@ -3601,6 +3852,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
|
||||
void Print::_make_skirt()
|
||||
{
|
||||
// Belt printer: skirt is not compatible.
|
||||
if (m_config.belt_printer.value)
|
||||
return;
|
||||
|
||||
const bool generate_skirt = this->has_skirt() || this->has_infinite_skirt();
|
||||
|
||||
// First off we need to decide how tall the skirt must be.
|
||||
@@ -4695,6 +4950,13 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
|
||||
// Wipe tower support.
|
||||
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_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
|
||||
return true;
|
||||
@@ -4707,6 +4969,7 @@ bool Print::has_wipe_tower() const
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
||||
{
|
||||
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
||||
@@ -4736,6 +4999,7 @@ bool Print::enable_timelapse_print() const
|
||||
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
|
||||
}
|
||||
|
||||
|
||||
void Print::_make_wipe_tower()
|
||||
{
|
||||
m_wipe_tower_data.clear();
|
||||
|
||||
Reference in New Issue
Block a user