mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 10:47:16 +00:00
Fix belt purge tower activation and placement safety
This commit is contained in:
10
result.json
10
result.json
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -42,10 +42,15 @@ bool Print::has_belt_purge_tower() const
|
|||||||
{
|
{
|
||||||
// Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower
|
// Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower
|
||||||
// option (not the classic enable_prime_tower).
|
// option (not the classic enable_prime_tower).
|
||||||
return m_config.belt_printer.value
|
if (!(m_config.belt_printer.value
|
||||||
&& m_config.enable_belt_purge_tower.value
|
&& m_config.enable_belt_purge_tower.value
|
||||||
&& !m_config.spiral_mode.value
|
&& !m_config.spiral_mode.value
|
||||||
&& m_config.filament_diameter.values.size() > 1;
|
&& m_config.filament_diameter.values.size() > 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return std::any_of(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
|
||||||
|
return object->config().belt_purge_tower_object.value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Belt mode: align ALL objects on the plate (the printed objects AND the purge
|
// Belt mode: align ALL objects on the plate (the printed objects AND the purge
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public:
|
|||||||
// tower to tell which prism fills carry purge vs. which are unclaimed waste.
|
// tower to tell which prism fills carry purge vs. which are unclaimed waste.
|
||||||
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
||||||
auto it = entity_map.find(std::make_tuple(entity, object));
|
auto it = entity_map.find(std::make_tuple(entity, object));
|
||||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
return it != entity_map.end() && copy_id < it->second.size() && it->second[copy_id] != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
||||||
|
|||||||
@@ -1477,13 +1477,13 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
|
|
||||||
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
|
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
|
||||||
&& m_config.print_sequence == PrintSequence::ByObject
|
&& m_config.print_sequence == PrintSequence::ByObject
|
||||||
&& extruders.size() > 1 && warning != nullptr) {
|
&& extruders.size() > 1) {
|
||||||
StringObjectException warningtemp;
|
StringObjectException warningtemp;
|
||||||
warningtemp.string = L("The belt purge tower is not generated in \"By object\" print sequence; "
|
warningtemp.string = L("The belt purge tower is not generated in \"By object\" print sequence; "
|
||||||
"filament changes will not be purged.");
|
"filament changes will not be purged.");
|
||||||
warningtemp.opt_key = "enable_belt_purge_tower";
|
warningtemp.opt_key = "enable_belt_purge_tower";
|
||||||
warningtemp.is_warning = true;
|
warningtemp.is_warning = true;
|
||||||
*warning = warningtemp;
|
add_warning(warningtemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_config.enable_prime_tower) {
|
if (m_config.enable_prime_tower) {
|
||||||
|
|||||||
@@ -142,7 +142,14 @@ bool ensure_belt_purge_tower(Model &model, PartPlateList &partplate_list, Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Sizing -----------------------------------------------------------
|
// --- Sizing -----------------------------------------------------------
|
||||||
const double width = print_config.has("belt_purge_tower_width") ? std::max(1., print_config.opt_float("belt_purge_tower_width")) : 35.;
|
const int n_islands = std::max(1, (int) filaments.size() - 1);
|
||||||
|
const double gap = 1.0;
|
||||||
|
// Every disconnected island needs at least 1 mm of printable width. Honor
|
||||||
|
// the configured total width whenever possible, but never let the island
|
||||||
|
// layout silently grow past the footprint used for placement.
|
||||||
|
const double min_width = n_islands + (n_islands - 1) * gap;
|
||||||
|
const double width = std::max(min_width,
|
||||||
|
print_config.has("belt_purge_tower_width") ? 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;
|
const double layer_h = print_config.has("layer_height") ? print_config.opt_float("layer_height") : 0.2;
|
||||||
|
|
||||||
// Belt geometry. The rotation axis is the gantry tilt axis; the belt
|
// Belt geometry. The rotation axis is the gantry tilt axis; the belt
|
||||||
@@ -219,6 +226,9 @@ bool ensure_belt_purge_tower(Model &model, PartPlateList &partplate_list, Object
|
|||||||
new_sig.key[4] = q(belt_max);
|
new_sig.key[4] = q(belt_max);
|
||||||
new_sig.key[5] = q(lat_min);
|
new_sig.key[5] = q(lat_min);
|
||||||
new_sig.key[6] = q(z_max);
|
new_sig.key[6] = q(z_max);
|
||||||
|
new_sig.key[7] = static_cast<long>(rot);
|
||||||
|
new_sig.key[8] = std::lround(theta * 10000.0);
|
||||||
|
new_sig.key[9] = q(lat_max);
|
||||||
if (prism_idxs.size() == 1 && new_sig == sig)
|
if (prism_idxs.size() == 1 && new_sig == sig)
|
||||||
return false; // already up to date — do not touch the model
|
return false; // already up to date — do not touch the model
|
||||||
|
|
||||||
@@ -279,15 +289,13 @@ bool ensure_belt_purge_tower(Model &model, PartPlateList &partplate_list, Object
|
|||||||
// lets each swap claim its own island. Total lateral footprint stays `width`
|
// lets each swap claim its own island. Total lateral footprint stays `width`
|
||||||
// (each island width/N wide, separated by a small gap), so per-layer capacity
|
// (each island width/N wide, separated by a small gap), so per-layer capacity
|
||||||
// per island ~= max_flush, matching the height sizing.
|
// per island ~= max_flush, matching the height sizing.
|
||||||
const int n_islands = std::max(1, (int) filaments.size() - 1);
|
|
||||||
// Minimal gap between sub-bars: they must stay just-separated so the slicer
|
// Minimal gap between sub-bars: they must stay just-separated so the slicer
|
||||||
// keeps them as distinct islands (hence distinct infill collections, one per
|
// keeps them as distinct islands (hence distinct infill collections, one per
|
||||||
// simultaneous swap). Zero gap would union them into one collection and
|
// simultaneous swap). Zero gap would union them into one collection and
|
||||||
// reintroduce the multi-swap-per-layer absorption bug; a hair over ~2 line
|
// reintroduce the multi-swap-per-layer absorption bug; a hair over ~2 line
|
||||||
// widths also keeps gap-fill from bridging them. 1 mm is about as close as
|
// widths also keeps gap-fill from bridging them. 1 mm is about as close as
|
||||||
// they can butt up while staying individually purgeable.
|
// they can butt up while staying individually purgeable.
|
||||||
const double gap = 1.0;
|
const double w_sub = (width - (n_islands - 1) * gap) / n_islands;
|
||||||
const double w_sub = std::max(1.0, (width - (n_islands - 1) * gap) / n_islands);
|
|
||||||
|
|
||||||
// --- (Re)create ---------------------------------------------------------
|
// --- (Re)create ---------------------------------------------------------
|
||||||
if (!prism_idxs.empty())
|
if (!prism_idxs.empty())
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#ifndef slic3r_GUI_BeltPurgeTower_hpp_
|
#pragma once
|
||||||
#define slic3r_GUI_BeltPurgeTower_hpp_
|
|
||||||
|
|
||||||
// ORCA-Belt: auto-managed purge prism for belt printers.
|
// ORCA-Belt: auto-managed purge prism for belt printers.
|
||||||
//
|
//
|
||||||
@@ -23,12 +22,12 @@ struct BeltPurgeSignature
|
|||||||
{
|
{
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
int filament_count = 0;
|
int filament_count = 0;
|
||||||
long key[7] = {0}; // rounded geometry inputs (0.1 mm units)
|
long key[10] = {0}; // rounded geometry inputs (0.1 mm units)
|
||||||
bool operator==(const BeltPurgeSignature &o) const
|
bool operator==(const BeltPurgeSignature &o) const
|
||||||
{
|
{
|
||||||
if (valid != o.valid || filament_count != o.filament_count)
|
if (valid != o.valid || filament_count != o.filament_count)
|
||||||
return false;
|
return false;
|
||||||
for (int i = 0; i < 7; ++i)
|
for (int i = 0; i < 10; ++i)
|
||||||
if (key[i] != o.key[i])
|
if (key[i] != o.key[i])
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@@ -44,5 +43,3 @@ bool ensure_belt_purge_tower(Model &model, PartPlateList &partplate_list, Object
|
|||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // slic3r_GUI_BeltPurgeTower_hpp_
|
|
||||||
|
|||||||
@@ -300,6 +300,41 @@ TEST_CASE("Print::validate tolerates a null warnings pointer", "[Print][validate
|
|||||||
CHECK(err.string.empty());
|
CHECK(err.string.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Purge tower selection keeps ordinary printers on the classic path", "[Print][PurgeTower][Regression]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = multifilament_config(2, {
|
||||||
|
{ "belt_printer", 0 },
|
||||||
|
{ "enable_prime_tower", 1 },
|
||||||
|
{ "enable_belt_purge_tower", 1 }
|
||||||
|
});
|
||||||
|
config.set_key_value("timelapse_type", new ConfigOptionEnum<TimelapseType>(TimelapseType::tlSmooth));
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
Print print;
|
||||||
|
build_cubes(model, print, config, /*n=*/1, /*overlap=*/false);
|
||||||
|
|
||||||
|
CHECK(print.has_wipe_tower());
|
||||||
|
CHECK_FALSE(print.has_belt_purge_tower());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Belt purge planning requires its managed purge object", "[Print][PurgeTower][Regression]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = multifilament_config(2, {
|
||||||
|
{ "belt_printer", 1 },
|
||||||
|
{ "enable_belt_purge_tower", 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
Print print;
|
||||||
|
build_cubes(model, print, config, /*n=*/1, /*overlap=*/false);
|
||||||
|
CHECK_FALSE(print.has_belt_purge_tower());
|
||||||
|
|
||||||
|
model.objects.front()->config.set_key_value("belt_purge_tower_object", new ConfigOptionBool(true));
|
||||||
|
print.apply(model, config);
|
||||||
|
CHECK(print.has_belt_purge_tower());
|
||||||
|
CHECK_FALSE(print.has_wipe_tower());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("A default slice emits perimeter, infill, and skirt", "[Print]")
|
TEST_CASE("A default slice emits perimeter, infill, and skirt", "[Print]")
|
||||||
{
|
{
|
||||||
const std::string gcode = slice({ cube(20) }, {
|
const std::string gcode = slice({ cube(20) }, {
|
||||||
|
|||||||
Reference in New Issue
Block a user