mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 00:31:19 +00:00
perf: speed up G-code export by 4-17% via parallel overhang precompute (#16050)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
co-authored by
Rodrigo Faselli
parent
a6dbf2502d
commit
c67b54b39d
@@ -7,6 +7,7 @@
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/GCode/ExtrusionProcessor.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
@@ -15,6 +16,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
@@ -538,6 +541,187 @@ TEST_CASE("A wall is left whole where neither its speed nor its cooling changes"
|
||||
REQUIRE(points.size() == 3);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// The caged overhang box, sliced, and a layer on its slope.
|
||||
struct SlicedCage
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
const PrintObject *object{nullptr};
|
||||
const Layer *layer{nullptr};
|
||||
|
||||
explicit SlicedCage(const DynamicPrintConfig &config = caged_overhang_config("classic"))
|
||||
{
|
||||
init_print(std::vector<TriangleMesh>{caged_overhang_mesh()}, print, model, config, nullptr, false);
|
||||
print.process();
|
||||
object = print.objects().front();
|
||||
layer = object->get_layer(int(std::lround((caged_slope_z_min + caged_slope_z_max) / 2. / caged_layer_height)));
|
||||
}
|
||||
};
|
||||
|
||||
using Walls = std::vector<std::vector<ProcessedPoint>>;
|
||||
|
||||
// Estimates every wall of `layer` against whatever layer `estimator` was last prepared with before it.
|
||||
Walls estimate_walls(ExtrusionQualityEstimator &estimator, const PrintObject *object, const Layer &layer)
|
||||
{
|
||||
const ConfigOptionPercents overlaps({90, 75, 50, 25, 13, 0});
|
||||
const ConfigOptionFloatsOrPercents speeds({FloatOrPercent{100, true}, FloatOrPercent{50, true}, FloatOrPercent{30, true},
|
||||
FloatOrPercent{20, true}, FloatOrPercent{10, true}, FloatOrPercent{5, true}});
|
||||
Walls walls;
|
||||
estimator.set_current_object(object);
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
for_each_extrusion_path(region->perimeters, [&](const ExtrusionPath &path) {
|
||||
if (is_perimeter(path.role()))
|
||||
walls.push_back(estimator.estimate_extrusion_quality(path, overlaps, speeds, caged_outer_wall_speed, caged_outer_wall_speed,
|
||||
true, 0.5f));
|
||||
});
|
||||
return walls;
|
||||
}
|
||||
|
||||
uint32_t float_bits(float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
return bits;
|
||||
}
|
||||
|
||||
bool same_point(const ProcessedPoint &a, const ProcessedPoint &b)
|
||||
{
|
||||
return a.p == b.p && float_bits(a.speed) == float_bits(b.speed) && float_bits(a.overlap) == float_bits(b.overlap);
|
||||
}
|
||||
|
||||
// Requires the walls to match point for point, bit for bit.
|
||||
void check_identical(const Walls &actual, const Walls &expected)
|
||||
{
|
||||
REQUIRE(actual.size() == expected.size());
|
||||
for (size_t wall = 0; wall < actual.size(); ++wall) {
|
||||
INFO("wall " << wall);
|
||||
REQUIRE(actual[wall].size() == expected[wall].size());
|
||||
for (size_t i = 0; i < actual[wall].size(); ++i) {
|
||||
const ProcessedPoint &a = actual[wall][i];
|
||||
const ProcessedPoint &e = expected[wall][i];
|
||||
INFO("point " << i << ": speed " << a.speed << " vs " << e.speed << ", overlap " << a.overlap << " vs " << e.overlap);
|
||||
CHECK(a.p == e.p);
|
||||
CHECK(float_bits(a.speed) == float_bits(e.speed));
|
||||
CHECK(float_bits(a.overlap) == float_bits(e.overlap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool any_difference(const Walls &a, const Walls &b)
|
||||
{
|
||||
return !std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const std::vector<ProcessedPoint> &wa, const std::vector<ProcessedPoint> &wb) {
|
||||
return std::equal(wa.begin(), wa.end(), wb.begin(), wb.end(), same_point);
|
||||
});
|
||||
}
|
||||
|
||||
bool any_slowed(const Walls &walls)
|
||||
{
|
||||
return std::any_of(walls.begin(), walls.end(), [](const std::vector<ProcessedPoint> &wall) {
|
||||
return std::any_of(wall.begin(), wall.end(), [](const ProcessedPoint &point) { return point.speed < caged_outer_wall_speed; });
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Overhang data computed ahead of the generator gives the same wall speeds", "[ExtrusionProcessor]")
|
||||
{
|
||||
const SlicedCage cage;
|
||||
REQUIRE(cage.layer->lower_layer != nullptr);
|
||||
|
||||
ExtrusionQualityEstimator queried;
|
||||
queried.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
|
||||
queried.prepare_for_new_layer(cage.object, cage.layer);
|
||||
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
|
||||
REQUIRE(any_slowed(expected));
|
||||
|
||||
ExtrusionQualityEstimator precomputed;
|
||||
precomputed.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
|
||||
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *cage.layer)});
|
||||
precomputed.prepare_for_new_layer(cage.object, cage.layer);
|
||||
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Overhang distances measured against another layer than the previous one are not used", "[ExtrusionProcessor]")
|
||||
{
|
||||
const SlicedCage cage;
|
||||
const Layer *two_below = cage.layer->lower_layer->lower_layer;
|
||||
REQUIRE(two_below != nullptr);
|
||||
|
||||
ExtrusionQualityEstimator queried;
|
||||
queried.prepare_for_new_layer(cage.object, two_below);
|
||||
queried.prepare_for_new_layer(cage.object, cage.layer);
|
||||
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
|
||||
ExtrusionQualityEstimator one_below;
|
||||
one_below.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
|
||||
one_below.prepare_for_new_layer(cage.object, cage.layer);
|
||||
REQUIRE(any_difference(estimate_walls(one_below, cage.object, *cage.layer), expected));
|
||||
|
||||
ExtrusionQualityEstimator precomputed;
|
||||
precomputed.prepare_for_new_layer(cage.object, two_below);
|
||||
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *cage.layer)});
|
||||
precomputed.prepare_for_new_layer(cage.object, cage.layer);
|
||||
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Overhang data computed for another layer is not used", "[ExtrusionProcessor]")
|
||||
{
|
||||
const SlicedCage cage;
|
||||
const Layer *one_below = cage.layer->lower_layer;
|
||||
REQUIRE(one_below != nullptr);
|
||||
REQUIRE(one_below->lower_layer != nullptr);
|
||||
|
||||
ExtrusionQualityEstimator queried;
|
||||
queried.prepare_for_new_layer(cage.object, one_below);
|
||||
queried.prepare_for_new_layer(cage.object, cage.layer);
|
||||
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
|
||||
ExtrusionQualityEstimator two_below;
|
||||
two_below.prepare_for_new_layer(cage.object, one_below->lower_layer);
|
||||
two_below.prepare_for_new_layer(cage.object, cage.layer);
|
||||
REQUIRE(any_difference(estimate_walls(two_below, cage.object, *cage.layer), expected));
|
||||
|
||||
ExtrusionQualityEstimator precomputed;
|
||||
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *one_below)});
|
||||
precomputed.prepare_for_new_layer(cage.object, one_below);
|
||||
precomputed.prepare_for_new_layer(cage.object, cage.layer);
|
||||
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Precomputed overhang data has the curled-line tree exactly when a region slows down for curled perimeters", "[ExtrusionProcessor]")
|
||||
{
|
||||
const bool slowdown = GENERATE(false, true);
|
||||
DynamicPrintConfig config = caged_overhang_config("classic");
|
||||
config.set_deserialize_strict("slowdown_for_curled_perimeters", slowdown ? "1" : "0");
|
||||
const SlicedCage cage(config);
|
||||
|
||||
GCode::LayerToPrint layer;
|
||||
layer.object_layer = cage.layer;
|
||||
layer.original_object = cage.object;
|
||||
const std::vector<PrecomputedOverhangLayer> precomputed = precompute_overhang_layers({layer}, false);
|
||||
REQUIRE(precomputed.size() == 1);
|
||||
CHECK((precomputed.front().lower_curled_lines != nullptr) == slowdown);
|
||||
}
|
||||
|
||||
TEST_CASE("Caged external overhangs are slowed when printed by object or through the pressure equalizer", "[ExtrusionProcessor]")
|
||||
{
|
||||
const auto [key, value] = GENERATE(table<const char *, const char *>({
|
||||
{"print_sequence", "by object"},
|
||||
{"max_volumetric_extrusion_rate_slope", "10"},
|
||||
}));
|
||||
INFO(key << " = " << value);
|
||||
DynamicPrintConfig config = caged_overhang_config("classic");
|
||||
config.set_deserialize_strict(key, value);
|
||||
Print print;
|
||||
Model model;
|
||||
init_print(std::vector<TriangleMesh>{caged_overhang_mesh()}, print, model, config, nullptr, false);
|
||||
|
||||
const std::vector<double> feed_rates = caged_slope_feed_rates(gcode(print));
|
||||
info_feed_rates("caged slope", feed_rates);
|
||||
REQUIRE_FALSE(feed_rates.empty());
|
||||
REQUIRE(*std::max_element(feed_rates.begin(), feed_rates.end()) < caged_slow_speed * MM_PER_MIN);
|
||||
}
|
||||
|
||||
TEST_CASE("Benchmark caged overhang interior sampling", "[ExtrusionProcessor][!benchmark]"){
|
||||
const char* wall_generator = GENERATE("classic", "arachne");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user