Print unsupported walls last (#15411)

This commit is contained in:
Ian Bassi
2026-09-17 09:14:20 -03:00
committed by GitHub
parent 82e91bd472
commit 59e40a2c2e
12 changed files with 353 additions and 2 deletions
+4
View File
@@ -454,6 +454,10 @@ class ExtrusionLoop : public ExtrusionEntity
{
public:
ExtrusionPaths paths;
// ORCA: Set on a loop extruded entirely in mid air and out of reach of the layer below: it has
// nothing to lean on until this layer is bridged, so the G-code writer holds it back until the
// infill is down. See defer_unsupported_loops() in PerimeterGenerator.cpp.
bool print_after_infill = false;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
+21 -1
View File
@@ -6603,6 +6603,8 @@ LayerResult GCode::process_layer(
}
// Then print infill
gcode += this->extrude_infill(print, by_region_specific, false);
// Then the walls left hanging in mid air, now that the infill can anchor them
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, false, true);
// Then print perimeters of regions that has is_infill_first == true
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, true);
}
@@ -6898,6 +6900,7 @@ LayerResult GCode::process_layer(
has_insert_timelapse_gcode = true;
}
gcode += this->extrude_infill(print, by_region_specific, false);
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, false, true);
gcode += this->extrude_perimeters(print, by_region_specific, first_layer, true);
// ironing
gcode += this->extrude_infill(print, by_region_specific, true);
@@ -7638,7 +7641,7 @@ std::string GCode::extrude_path(const ExtrusionPath& path, const std::string& de
}
// Extrude perimeters: Decide where to put seams (hide or align seams).
std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool is_first_layer, bool is_infill_first)
std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool is_first_layer, bool is_infill_first, bool unsupported_loops_only)
{
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region)
@@ -7657,7 +7660,24 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
m_config.wipe_inward_distance.value > 0. &&
scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON)
wipe_support.emplace();
// ORCA: loops flagged as extruded in mid air, out of reach of the layer below, are held back
// for a second pass after the infill that anchors them. Infill already precedes infill first walls.
const bool defer_unsupported = !is_infill_first;
auto waits_for_infill = [](const ExtrusionEntity *ee) {
return ee->is_loop() && static_cast<const ExtrusionLoop *>(ee)->print_after_infill;
};
// The deferred pass runs after the infill, so the loops the first pass emitted are
// already down and belong in the prefix an inward wipe may land on.
if (wipe_support && defer_unsupported && unsupported_loops_only)
for (const ExtrusionEntity* ee : region.perimeters)
if (!waits_for_infill(ee))
wipe_support->append(*ee);
for (const ExtrusionEntity* ee : region.perimeters) {
if (defer_unsupported && waits_for_infill(ee) != unsupported_loops_only)
continue;
gcode += this->extrude_entity(*ee, "perimeter", -1., region.perimeters,
wipe_support ? &*wipe_support : nullptr);
if (wipe_support)
+1 -1
View File
@@ -534,7 +534,7 @@ private:
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx);
std::string extrude_perimeters(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool is_first_layer, bool is_infill_first);
std::string extrude_perimeters(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool is_first_layer, bool is_infill_first, bool unsupported_loops_only = false);
std::string extrude_infill(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool ironing);
std::string extrude_support(const ExtrusionEntityCollection& support_fills, const ExtrusionRole support_extrusion_role);
+1
View File
@@ -153,6 +153,7 @@ bool Layer::is_perimeter_compatible(const Print& print, const PrintRegion& a, co
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
&& config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
&& config.detect_overhang_wall == other_config.detect_overhang_wall
&& config.unsupported_wall_last == other_config.unsupported_wall_last
&& config.overhang_reverse == other_config.overhang_reverse
&& config.overhang_reverse_threshold == other_config.overhang_reverse_threshold
&& config.wall_direction == other_config.wall_direction
+71
View File
@@ -550,6 +550,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
if (!paths.empty()) {
if (extrusion->is_closed) {
ExtrusionLoop extrusion_loop(std::move(paths), pg_extrusion.is_contour ? elrDefault : elrHole);
extrusion_loop.inset_idx = extrusion->inset_idx;
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) ==
(pg_extrusion.is_contour || pg_extrusions.size() == 2))
extrusion_loop.make_counter_clockwise();
@@ -1318,6 +1319,73 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_
}
}
// A loop made of nothing but overhang paths lies entirely off the lower layer.
static bool is_unsupported_loop(const ExtrusionEntity *entity)
{
if (!entity->is_loop())
return false;
const ExtrusionPaths &paths = static_cast<const ExtrusionLoop *>(entity)->paths;
return !paths.empty() && std::all_of(paths.begin(), paths.end(),
[](const ExtrusionPath &path) { return path.role() == erOverhangPerimeter; });
}
// ORCA: A wall loop with nothing under it has nothing to lean on, so whatever the configured wall
// sequence it is extruded after the loops that anchor it, innermost first. A loop that runs alongside
// an anchored one belongs to the same wall stack and keeps its place ahead of the infill, which needs
// it as an anchor; one that touches nothing has only that infill to rest on, so it is flagged for the
// G-code writer to hold it back until the infill is down.
static void defer_unsupported_loops(const PerimeterGenerator &perimeter_generator, ExtrusionEntityCollection &entities)
{
if (!perimeter_generator.config->unsupported_wall_last)
return;
ExtrusionEntitiesPtr &src = entities.entities;
auto first_deferred = std::stable_partition(src.begin(), src.end(),
[](const ExtrusionEntity *entity) { return !is_unsupported_loop(entity); });
if (first_deferred == src.end())
return;
std::stable_sort(first_deferred, src.end(),
[](const ExtrusionEntity *lhs, const ExtrusionEntity *rhs) { return lhs->inset_idx > rhs->inset_idx; });
auto collect_lines = [](const ExtrusionEntity *entity, Lines &out) {
Polylines polylines;
entity->collect_polylines(polylines);
append(out, to_lines(polylines));
};
Lines anchored;
for (auto it = src.begin(); it != first_deferred; ++it)
collect_lines(*it, anchored);
std::vector<ExtrusionLoop *> unattached;
for (auto it = first_deferred; it != src.end(); ++it)
unattached.emplace_back(static_cast<ExtrusionLoop *>(*it));
// A loop leaning on a loop that is itself anchored is anchored as well, so spread outwards from
// the anchored loops until no unsupported loop is left touching what was reached.
const double touch_distance = 1.5 * std::max(perimeter_generator.ext_perimeter_flow.scaled_spacing(),
perimeter_generator.perimeter_flow.scaled_spacing());
while (!anchored.empty()) {
AABBTreeLines::LinesDistancer<Line> distancer{std::move(anchored)};
anchored.clear();
for (ExtrusionLoop *&loop : unattached) {
if (loop == nullptr)
continue;
const Points points = loop->as_polyline().points;
if (std::any_of(points.begin(), points.end(),
[&distancer, touch_distance](const Point &point) { return distancer.distance_from_lines<false>(point) < touch_distance; })) {
collect_lines(loop, anchored);
loop = nullptr;
}
}
}
for (ExtrusionLoop *loop : unattached)
if (loop != nullptr)
loop->print_after_infill = true;
}
void PerimeterGenerator::process_classic()
{
group_region_by_fuzzify(*this);
@@ -1804,6 +1872,8 @@ void PerimeterGenerator::process_classic()
}
}
defer_unsupported_loops(*this, entities);
// append perimeters for this slice as a collection
if (! entities.empty())
this->loops->append(entities);
@@ -2742,6 +2812,7 @@ void PerimeterGenerator::process_arachne()
reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole,
this->config->overhang_reverse_internal_only);
}
defer_unsupported_loops(*this, extrusion_coll);
this->loops->append(extrusion_coll);
}
+1
View File
@@ -1058,6 +1058,7 @@ static std::vector<std::string> s_Preset_print_options{
"reduce_crossing_wall",
"detect_thin_wall",
"detect_overhang_wall",
"unsupported_wall_last",
"overhang_reverse",
"overhang_reverse_threshold",
"overhang_reverse_internal_only",
+10
View File
@@ -5547,6 +5547,16 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("unsupported_wall_last", coBool);
def->label = L("Print unsupported walls last");
def->category = L("Quality");
def->tooltip = L("Wall loops that lie entirely in mid air are printed once something can hold them:\n"
"they are extruded after the other walls of their island, innermost first, whatever the wall order is.\n"
"A loop that only the bridges of this layer can anchor waits until those bridges are printed, while a loop running "
"alongside a supported wall keeps its place before the infill, which needs it as an anchor.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("outer_wall_filament_id", coInt);
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
def->label = L("Outer walls");
+1
View File
@@ -1353,6 +1353,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloatsNullable, filament_ironing_speed))
// Detect bridging perimeters
((ConfigOptionBool, detect_overhang_wall))
((ConfigOptionBool, unsupported_wall_last))
((ConfigOptionInt, outer_wall_filament_id))
((ConfigOptionInt, inner_wall_filament_id))
((ConfigOptionFloatOrPercent, inner_wall_line_width))
+1
View File
@@ -1501,6 +1501,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "fuzzy_skin_octaves"
|| opt_key == "fuzzy_skin_persistence"
|| opt_key == "detect_overhang_wall"
|| opt_key == "unsupported_wall_last"
|| opt_key == "overhang_reverse"
|| opt_key == "overhang_reverse_internal_only"
|| opt_key == "overhang_reverse_threshold"
+1
View File
@@ -1134,6 +1134,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool has_detect_overhang_wall = config->opt_bool("detect_overhang_wall");
bool has_overhang_reverse = config->opt_bool("overhang_reverse");
bool allow_overhang_reverse = !has_spiral_vase;
toggle_line("unsupported_wall_last", has_detect_overhang_wall);
toggle_line("overhang_reverse", allow_overhang_reverse);
toggle_line("overhang_reverse_internal_only", allow_overhang_reverse && has_overhang_reverse);
bool has_overhang_reverse_internal_only = config->opt_bool("overhang_reverse_internal_only");
+1
View File
@@ -2794,6 +2794,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Overhangs"), L"param_overhang");
optgroup->append_single_option_line("detect_overhang_wall", "quality_settings_overhangs#detect-overhang-wall");
optgroup->append_single_option_line("unsupported_wall_last", "quality_settings_overhangs#unsupported-wall-last");
optgroup->append_single_option_line("make_overhang_printable", "quality_settings_overhangs#make-overhang-printable");
optgroup->append_single_option_line("make_overhang_printable_angle", "quality_settings_overhangs#maximum-angle");
optgroup->append_single_option_line("make_overhang_printable_hole_size", "quality_settings_overhangs#hole-area");