mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 22:31:02 +00:00
perf: speed up G-code export by 3-9% via cached config lookups (#16028)
This commit is contained in:
+33
-25
@@ -4334,9 +4334,9 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const
|
||||
|
||||
size_t GCode::get_filament_config_index(int filament_id) const
|
||||
{
|
||||
if (m_print) {
|
||||
return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx);
|
||||
}
|
||||
if (m_print)
|
||||
return m_filament_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(),
|
||||
[&] { return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx); });
|
||||
// Orca: without a Print the filament-indexed arrays are unexpanded, so the
|
||||
// filament id itself is the only meaningful column.
|
||||
return filament_id;
|
||||
@@ -4350,9 +4350,9 @@ size_t GCode::get_filament_config_index(int filament_id, size_t layer_id) const
|
||||
|
||||
size_t GCode::get_nozzle_config_index(int filament_id) const
|
||||
{
|
||||
if (m_print) {
|
||||
return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx);
|
||||
}
|
||||
if (m_print)
|
||||
return m_nozzle_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(),
|
||||
[&] { return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx); });
|
||||
// Orca: same reasoning; degenerate to the filament's extruder column.
|
||||
return get_extruder_id(filament_id);
|
||||
}
|
||||
@@ -8059,28 +8059,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
unsigned int acceleration_i = 0;
|
||||
double jerk = 0;
|
||||
// adjust acceleration
|
||||
if (NOZZLE_CONFIG(default_acceleration) > 0) {
|
||||
const size_t nozzle = get_nozzle_config_index(m_writer.filament()->id());
|
||||
if (m_config.default_acceleration.get_at(nozzle) > 0) {
|
||||
const ExtrusionRole role = path.role();
|
||||
const double bridge_acceleration = is_bridge(role) ?
|
||||
m_config.bridge_acceleration.get_at(nozzle).get_abs_value(m_config.outer_wall_acceleration.get_at(nozzle)) : 0.;
|
||||
const double sparse_infill_acceleration = role == erInternalInfill ?
|
||||
m_config.sparse_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.;
|
||||
const double internal_solid_infill_acceleration = role == erSolidInfill ?
|
||||
m_config.internal_solid_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.;
|
||||
double acceleration;
|
||||
if (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) {
|
||||
acceleration = NOZZLE_CONFIG(initial_layer_acceleration);
|
||||
if (this->on_first_layer() && m_config.initial_layer_acceleration.get_at(nozzle) > 0) {
|
||||
acceleration = m_config.initial_layer_acceleration.get_at(nozzle);
|
||||
#if 0
|
||||
} else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
|
||||
acceleration = m_config.first_layer_acceleration_over_raft.value;
|
||||
#endif
|
||||
} else if (m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && is_bridge(path.role())) {
|
||||
acceleration = m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
|
||||
} else if (m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erInternalInfill)) {
|
||||
acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
|
||||
} else if (m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erSolidInfill)) {
|
||||
acceleration = m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
|
||||
} else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 && is_external_perimeter(path.role())) {
|
||||
acceleration = NOZZLE_CONFIG(outer_wall_acceleration);
|
||||
} else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && is_internal_perimeter(path.role())) {
|
||||
acceleration = NOZZLE_CONFIG(inner_wall_acceleration);
|
||||
} else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) {
|
||||
acceleration = NOZZLE_CONFIG(top_surface_acceleration);
|
||||
} else if (bridge_acceleration > 0) {
|
||||
acceleration = bridge_acceleration;
|
||||
} else if (sparse_infill_acceleration > 0) {
|
||||
acceleration = sparse_infill_acceleration;
|
||||
} else if (internal_solid_infill_acceleration > 0) {
|
||||
acceleration = internal_solid_infill_acceleration;
|
||||
} else if (m_config.outer_wall_acceleration.get_at(nozzle) > 0 && is_external_perimeter(role)) {
|
||||
acceleration = m_config.outer_wall_acceleration.get_at(nozzle);
|
||||
} else if (m_config.inner_wall_acceleration.get_at(nozzle) > 0 && is_internal_perimeter(role)) {
|
||||
acceleration = m_config.inner_wall_acceleration.get_at(nozzle);
|
||||
} else if (m_config.top_surface_acceleration.get_at(nozzle) > 0 && is_top_surface(role)) {
|
||||
acceleration = m_config.top_surface_acceleration.get_at(nozzle);
|
||||
} else {
|
||||
acceleration = NOZZLE_CONFIG(default_acceleration);
|
||||
acceleration = m_config.default_acceleration.get_at(nozzle);
|
||||
}
|
||||
acceleration_i = (unsigned int)floor(acceleration + 0.5);
|
||||
}
|
||||
@@ -8181,7 +8189,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed));
|
||||
}
|
||||
} else if(path.role() == erInternalBridgeInfill) {
|
||||
speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
|
||||
speed = m_config.internal_bridge_speed.get_at(nozzle).get_abs_value(m_config.bridge_speed.get_at(nozzle));
|
||||
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
|
||||
speed = NOZZLE_CONFIG(bridge_speed);
|
||||
} else if (path.role() == erInternalInfill) {
|
||||
@@ -8193,7 +8201,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
} else if (path.role() == erIroning) {
|
||||
const size_t filament_idx = get_filament_config_index(m_writer.filament()->id());
|
||||
speed = m_config.filament_ironing_speed.is_nil(filament_idx)
|
||||
? m_config.get_abs_value("ironing_speed")
|
||||
? m_config.ironing_speed.value
|
||||
: m_config.filament_ironing_speed.get_at(filament_idx);
|
||||
} else if (path.role() == erBottomSurface) {
|
||||
speed = NOZZLE_CONFIG(initial_layer_infill_speed);
|
||||
@@ -8254,7 +8262,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
// Override skirt speed if set
|
||||
if (path.role() == erSkirt) {
|
||||
const double skirt_speed = m_config.get_abs_value("skirt_speed");
|
||||
const double skirt_speed = m_config.skirt_speed.value;
|
||||
if (skirt_speed > 0.0)
|
||||
speed = skirt_speed;
|
||||
}
|
||||
|
||||
@@ -301,6 +301,24 @@ public:
|
||||
size_t get_filament_config_index(int filament_id, size_t layer_id) const;
|
||||
size_t get_nozzle_config_index(int filament_id) const;
|
||||
|
||||
// Holds the last slot a resolver returned without locking, so only the G-code generator may
|
||||
// call the resolvers.
|
||||
struct ConfigIndexCache
|
||||
{
|
||||
bool valid{false};
|
||||
int filament_id{0};
|
||||
size_t layer_idx{0};
|
||||
size_t generation{0};
|
||||
size_t index{0};
|
||||
|
||||
template<class Lookup> size_t get(int filament, size_t layer, size_t gen, Lookup &&lookup)
|
||||
{
|
||||
if (!valid || filament_id != filament || layer_idx != layer || generation != gen)
|
||||
*this = {true, filament, layer, gen, size_t(lookup())};
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
// public, so that it could be accessed by free helper functions from GCode.cpp
|
||||
struct LayerToPrint
|
||||
@@ -784,6 +802,8 @@ private:
|
||||
// Object layer id of the layer being generated; keys the per-filament config-slot
|
||||
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
|
||||
size_t m_cur_layer_idx{0};
|
||||
mutable ConfigIndexCache m_filament_index_cache;
|
||||
mutable ConfigIndexCache m_nozzle_index_cache;
|
||||
|
||||
std::set<unsigned int> m_initial_layer_extruders;
|
||||
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
|
||||
|
||||
@@ -4298,10 +4298,11 @@ Polygons Print::get_extruder_shared_printable_polygon() const
|
||||
return shared_printable_polys;
|
||||
}
|
||||
|
||||
// Narrow the stored grouping result to the layer-aware type the slicing pipeline uses.
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> Print::get_layered_nozzle_group_result() const
|
||||
void Print::set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result)
|
||||
{
|
||||
return std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result);
|
||||
m_nozzle_group_result = std::move(result);
|
||||
m_layered_nozzle_group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result);
|
||||
++m_config_index_generation;
|
||||
}
|
||||
|
||||
// Dynamic (per-layer selector) regroup predicate.
|
||||
@@ -4337,6 +4338,7 @@ int Print::get_filament_config_indx(int filament_id, int layer_id, bool use_cach
|
||||
void Print::update_filament_self_index_cache()
|
||||
{
|
||||
m_missing_nozzle_group_logged.clear(); // reset the per-slice get_config_index log dedupe
|
||||
++m_config_index_generation;
|
||||
|
||||
std::vector<int> values;
|
||||
if (m_full_print_config.has("filament_self_index")) {
|
||||
@@ -4376,7 +4378,7 @@ int Print::get_nozzle_config_index(int filament_id, int layer_id)
|
||||
|
||||
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap *index_map)
|
||||
{
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get();
|
||||
// Orca: defensive — when no grouping producer has published a result yet, fall back to the
|
||||
// static identity: one filament-variant column per filament.
|
||||
if (!group_result)
|
||||
@@ -4411,7 +4413,7 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
|
||||
|
||||
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map)
|
||||
{
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get();
|
||||
// Orca: same static fallback as the filament overload; the slot degenerates to the filament's
|
||||
// extruder column (filament_map is 1 based, get_extruder_id guards the filament id range).
|
||||
if (!group_result)
|
||||
|
||||
@@ -1073,9 +1073,9 @@ public:
|
||||
|
||||
// Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder.
|
||||
// Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc.
|
||||
void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result) { m_nozzle_group_result = result; }
|
||||
void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result);
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> get_nozzle_group_result() const { return m_nozzle_group_result; }
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const;
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const { return m_layered_nozzle_group_result; }
|
||||
|
||||
// True only when the project opts into the per-layer filament selector
|
||||
// (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the
|
||||
@@ -1226,6 +1226,9 @@ public:
|
||||
// pipeline's cooling stage, which runs concurrently with the generator stage filling it.
|
||||
int get_filament_config_indx(int filament_id, int layer_id, bool use_cache = true);
|
||||
int get_nozzle_config_index(int filament_id, int layer_id);
|
||||
// Changes with the grouping result and the filament maps, so a caller may reuse a resolved slot
|
||||
// until it changes.
|
||||
size_t config_index_generation() const { return m_config_index_generation; }
|
||||
|
||||
// Orca: Implement prusa's filament shrink compensation approach
|
||||
// Returns if all used filaments have same shrinkage compensations.
|
||||
@@ -1352,6 +1355,9 @@ private:
|
||||
|
||||
// Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
// m_nozzle_group_result narrowed to the layer-aware type; only set_nozzle_group_result() assigns
|
||||
// either.
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> m_layered_nozzle_group_result;
|
||||
|
||||
// Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings().
|
||||
// Rebuilt (or cleared) on every process().
|
||||
@@ -1361,6 +1367,7 @@ private:
|
||||
FilamentIndexMap m_filament_index_map;
|
||||
// Used to cache printer and process parameter information
|
||||
PrintIndexMap m_nozzle_index_map;
|
||||
size_t m_config_index_generation{0};
|
||||
// Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index()
|
||||
// falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per
|
||||
// filament instead of flooding thousands of identical error lines. Cleared with the caches each slice.
|
||||
|
||||
@@ -1309,7 +1309,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
|
||||
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
|
||||
// the full config — instead of trimming back to one slot per filament.
|
||||
auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
|
||||
auto group_result = this->get_layered_nozzle_group_result();
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||
if (group_result && group_result->is_support_dynamic_nozzle_map()
|
||||
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
@@ -97,6 +100,22 @@ SCENARIO("Origin manipulation", "[GCodeWriter]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("A cached config slot is looked up again whenever its key changes", "[GCodeWriter]")
|
||||
{
|
||||
GCode::ConfigIndexCache cache;
|
||||
int lookups = 0;
|
||||
auto slot = [&](int filament, size_t layer, size_t generation) {
|
||||
return cache.get(filament, layer, generation, [&] { ++lookups; return filament * 100 + int(layer) * 10 + int(generation); });
|
||||
};
|
||||
REQUIRE(slot(1, 2, 3) == 123);
|
||||
REQUIRE(slot(1, 2, 3) == 123);
|
||||
REQUIRE(lookups == 1);
|
||||
REQUIRE(slot(4, 2, 3) == 423);
|
||||
REQUIRE(slot(4, 5, 3) == 453);
|
||||
REQUIRE(slot(4, 5, 6) == 456);
|
||||
REQUIRE(lookups == 4);
|
||||
}
|
||||
|
||||
// Verify that emit_machine_limits_to_gcode emits the correct max value across
|
||||
// used extruders (regression for commit b4ee665: "Emit max value of machine
|
||||
// limit among used extruders").
|
||||
@@ -872,3 +891,46 @@ TEST_CASE("Custom G-code motion limits are restored before generated moves", "[G
|
||||
REQUIRE(gcode.find("M204 S6000 ; adjust acceleration", custom_gcode_pos) != std::string::npos);
|
||||
REQUIRE(gcode.find("M205 X8 Y8 ; adjust jerk", custom_gcode_pos) != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("Percent accelerations resolve against the option they are a percentage of", "[GCodeWriter]")
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "gcode_flavor", "marlin" },
|
||||
{ "machine_max_acceleration_extruding", "20000,20000" },
|
||||
{ "default_acceleration", "4000" },
|
||||
{ "initial_layer_acceleration", "0" },
|
||||
{ "outer_wall_acceleration", "3000" },
|
||||
{ "bridge_acceleration", "50%" },
|
||||
{ "sparse_infill_acceleration", "25%" },
|
||||
{ "internal_solid_infill_acceleration", "60%" },
|
||||
{ "sparse_infill_density", "20%" },
|
||||
});
|
||||
// get_abs_value_at() resolves each percentage through the ratio_over in the config definitions.
|
||||
const std::map<std::string, int> expected = {
|
||||
{ "Bridge", int(config.get_abs_value_at("bridge_acceleration", 0)) },
|
||||
{ "Sparse infill", int(config.get_abs_value_at("sparse_infill_acceleration", 0)) },
|
||||
{ "Internal solid infill", int(config.get_abs_value_at("internal_solid_infill_acceleration", 0)) },
|
||||
};
|
||||
REQUIRE(expected.at("Bridge") == 1500);
|
||||
REQUIRE(expected.at("Sparse infill") == 1000);
|
||||
REQUIRE(expected.at("Internal solid infill") == 2400);
|
||||
|
||||
std::map<std::string, std::set<int>> accelerations_by_role;
|
||||
std::string role;
|
||||
int acceleration = 0;
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(Slic3r::Test::slice({ TestMesh::bridge }, config), [&](GCodeReader &self, const GCodeReader::GCodeLine &line) {
|
||||
float value;
|
||||
if (boost::starts_with(line.raw(), ";TYPE:"))
|
||||
role = line.raw().substr(6);
|
||||
else if (line.cmd_is("M204") && line.has_value('S', value))
|
||||
acceleration = int(value);
|
||||
else if (line.extruding(self) && line.dist_XY(self) > 0)
|
||||
accelerations_by_role[role].insert(acceleration);
|
||||
});
|
||||
for (const auto &[role_name, value] : expected) {
|
||||
INFO(role_name);
|
||||
REQUIRE(accelerations_by_role[role_name] == std::set<int>{ value });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,6 +510,22 @@ TEST_CASE("Print config-index resolvers pick per-filament Hybrid slots", "[Print
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Regrouping or rewriting the filament maps changes the config-index generation", "[Print][H2C]")
|
||||
{
|
||||
Model model;
|
||||
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
|
||||
Print print;
|
||||
print.apply(model, DynamicPrintConfig::full_print_config());
|
||||
|
||||
size_t generation = print.config_index_generation();
|
||||
print.set_nozzle_group_result(nullptr);
|
||||
REQUIRE(print.config_index_generation() != generation);
|
||||
|
||||
generation = print.config_index_generation();
|
||||
print.update_filament_maps_to_config({1}, {(int) nvtStandard}, {0});
|
||||
REQUIRE(print.config_index_generation() != generation);
|
||||
}
|
||||
|
||||
TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid", "[Print][H2C]")
|
||||
{
|
||||
// apply() rebuilds m_config.filament_map_2 to the real per-filament slot map, while the
|
||||
|
||||
Reference in New Issue
Block a user