Merge upstream, including texture displacement and the instance lock

138 commits. The count is large because texture displacement merged with its
whole branch history behind it, going back to July, alongside config and preset
file locking across instances, a foundation for configurable printer agent
connections, and a day of smaller fixes and CI work.

Two conflicts, both the same shape: each side had appended to a sorted list and
git could not choose an order. libslic3r's CMakeLists gained InstanceLock
alongside our IMEXHelpers and IMEXZones, and the preset bundle loading test
gained an include for ParallelResolve alongside ours for IMEXHelpers. Both sides
kept, alphabetical. No logic conflicted.

Verified: 740 targets build clean under -Werror, and the Release suite passes
1740 of 1740, up from 1665 -- the 75 new cases arrived with the merge and all
pass. That mattered more than usual here, since preset loading and config
locking are both areas the IMEX preset code touches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-10-01 18:46:19 -04:00
co-authored by Claude Opus 5
1019 changed files with 50005 additions and 574 deletions
+61 -32
View File
@@ -1260,7 +1260,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp));
int interface_temp = full_config.filament_tower_interface_print_temp.get_at(new_filament_id);
if (interface_temp == -1)
interface_temp = full_config.nozzle_temperature_range_high.get_at(new_filament_id);
interface_temp = full_config.nozzle_temperature_range_high.get_at(new_fi);
if (full_config.enable_tower_interface_features && tcr.is_contact)
new_filament_temp = interface_temp;
config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp));
@@ -1311,7 +1311,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
flush_temps[idx] = use_fast_flush ? m_print_config->filament_flush_temp_fast.get_at(fi)
: m_print_config->filament_flush_temp.get_at(fi);
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx);
flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(fi);
filament_cooling_before_tower[idx] = m_print_config->filament_cooling_before_tower.get_at(fi);
}
if (tcr.is_contact || gcodegen.m_layer_index == 0)
@@ -1565,7 +1565,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
const bool will_go_down = !is_approx(z, current_z);
const bool is_ramming = (gcodegen.config().single_extruder_multi_material) ||
(!gcodegen.config().single_extruder_multi_material &&
gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
gcodegen.config().filament_multitool_ramming.get_at(gcodegen.get_filament_config_index(tcr.initial_tool)));
// Orca: user-facing override (Printer Settings > Wipe tower > "Tool change on wipe tower").
// Forces the toolhead to travel over the wipe tower before issuing Tx even on multi-toolhead
// printers without ramming, where Orca would otherwise emit Tx in place (potentially over the part).
@@ -1622,7 +1622,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
if (gcodegen.config().enable_tower_interface_features && tcr.is_contact) {
interface_temp = gcodegen.config().filament_tower_interface_print_temp.get_at(new_extruder_id);
if (interface_temp == -1)
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id);
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(gcodegen.get_filament_config_index(new_extruder_id));
toolchange_temp_override = interface_temp;
}
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override,
@@ -4536,6 +4536,12 @@ size_t GCode::get_filament_config_index(int filament_id) const
return filament_id;
}
size_t GCode::get_filament_config_index(int filament_id, size_t layer_id) const
{
// Orca: uncached, as the stages after the generator run concurrently with it.
return m_print ? m_print->get_filament_config_indx(filament_id, (int) layer_id, false) : filament_id;
}
size_t GCode::get_nozzle_config_index(int filament_id) const
{
if (m_print) {
@@ -7564,12 +7570,12 @@ static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& la
return out;
}
std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters,
const Point* start_point,
const WipeInwardSupport* wipe_support)
std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const std::string& description,
double speed,
const std::vector<const ExtrusionEntity*>& region_perimeters,
const Point* start_point,
const WipeInwardSupport* wipe_support)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
@@ -7913,11 +7919,11 @@ std::string GCode::extrude_multi_path(const ExtrusionMultiPath& multipath, const
return gcode;
}
std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters,
const WipeInwardSupport* wipe_support)
std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const std::vector<const ExtrusionEntity*>& region_perimeters,
const WipeInwardSupport* wipe_support)
{
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
return this->extrude_path(*path, description, speed);
@@ -8008,23 +8014,32 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing)
{
std::string gcode;
ExtrusionEntitiesPtr extrusions;
const char* extrusion_name = ironing ? "ironing" : "infill";
std::string gcode;
std::vector<const ExtrusionEntity*> extrusions;
std::vector<std::unique_ptr<ExtrusionEntity>> reversed;
const char* extrusion_name = ironing ? "ironing" : "infill";
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.infills.empty()) {
extrusions.clear();
extrusions.reserve(region.infills.size());
for (ExtrusionEntity *ee : region.infills)
for (const ExtrusionEntity *ee : region.infills)
if ((ee->role() == erIroning) == ironing)
extrusions.emplace_back(ee);
if (! extrusions.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
reversed.clear();
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point(), reversed);
// The reversed copies are in chain order.
auto next_reversed = reversed.begin();
for (const ExtrusionEntity *fill : extrusions) {
ExtrusionEntity *own_copy = next_reversed != reversed.end() && next_reversed->get() == fill ? (next_reversed++)->get() : nullptr;
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos.to_point()).entities)
// A reversed copy is owned here and can be moved from.
ExtrusionEntityCollection chained = own_copy ? std::move(static_cast<ExtrusionEntityCollection&>(*own_copy)) : ExtrusionEntityCollection(*eec);
if (!chained.no_sort)
chain_and_reorder_extrusion_entities(chained.entities, m_last_pos.to_point());
for (ExtrusionEntity *ee : chained.entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
@@ -8062,9 +8077,9 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
std::string gcode;
if (!support_fills.entities.empty()) {
ExtrusionEntitiesPtr extrusions;
std::vector<const ExtrusionEntity*> extrusions;
extrusions.reserve(support_fills.entities.size());
for (ExtrusionEntity* ee : support_fills.entities) {
for (const ExtrusionEntity* ee : support_fills.entities) {
const auto role = ee->role();
if ((role == support_extrusion_role) || (support_extrusion_role == erMixed && role != erIroning)) {
extrusions.emplace_back(ee);
@@ -8073,9 +8088,10 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
if (extrusions.empty())
return gcode;
std::vector<std::unique_ptr<ExtrusionEntity>> reversed;
//ORCA: Respect no_sort to preserve support base outline->fill order.
if (!support_fills.no_sort)
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point(), reversed);
for (const ExtrusionEntity *ee : extrusions) {
ExtrusionRole role = ee->role();
@@ -9669,10 +9685,13 @@ void GCode::update_placeholder_parser_with_variant_params()
// Helper: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly.
std::vector<size_t> config_index(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
config_index[i] = get_filament_config_index(i);
auto remap_by_filament = [&](const auto &src) {
std::decay_t<decltype(src.values)> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
dst[i] = src.get_at(config_index[i]);
return dst;
};
@@ -9687,6 +9706,16 @@ void GCode::update_placeholder_parser_with_variant_params()
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
this->placeholder_parser().set("fan_min_speed", new ConfigOptionFloats(remap_by_filament(m_config.fan_min_speed)));
this->placeholder_parser().set("fan_max_speed", new ConfigOptionFloats(remap_by_filament(m_config.fan_max_speed)));
this->placeholder_parser().set("additional_cooling_fan_speed", new ConfigOptionInts(remap_by_filament(m_config.additional_cooling_fan_speed)));
this->placeholder_parser().set("filament_minimal_purge_on_wipe_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_minimal_purge_on_wipe_tower)));
this->placeholder_parser().set("filament_multitool_ramming", new ConfigOptionBools(remap_by_filament(m_config.filament_multitool_ramming)));
this->placeholder_parser().set("filament_multitool_ramming_volume", new ConfigOptionFloats(remap_by_filament(m_config.filament_multitool_ramming_volume)));
this->placeholder_parser().set("filament_multitool_ramming_flow", new ConfigOptionFloats(remap_by_filament(m_config.filament_multitool_ramming_flow)));
this->placeholder_parser().set("nozzle_temperature_range_low", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_range_low)));
const auto nozzle_temperature_range_high = remap_by_filament(m_config.nozzle_temperature_range_high);
this->placeholder_parser().set("nozzle_temperature_range_high", new ConfigOptionInts(nozzle_temperature_range_high));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
@@ -9707,7 +9736,7 @@ void GCode::update_placeholder_parser_with_variant_params()
if (flush_v_speed[i] == 0)
flush_v_speed[i] = filament_max_v[i];
if (flush_temps[i] == 0)
flush_temps[i] = m_config.nozzle_temperature_range_high.get_at(i);
flush_temps[i] = nozzle_temperature_range_high[i];
}
this->placeholder_parser().set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
this->placeholder_parser().set("flush_temperatures", new ConfigOptionInts(flush_temps));
@@ -9998,7 +10027,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
{
int interface_temp = m_config.filament_tower_interface_print_temp.get_at(new_filament_id);
if (interface_temp == -1)
interface_temp = m_config.nozzle_temperature_range_high.get_at(new_filament_id);
interface_temp = m_config.nozzle_temperature_range_high.get_at(new_fi);
dyn_config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp));
}
if (toolchange_temp_override > 0) {
@@ -10037,7 +10066,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
flush_temps[idx] = use_fast_flush ? m_print->config().filament_flush_temp_fast.get_at(fi)
: m_print->config().filament_flush_temp.get_at(fi);
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx);
flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(fi);
filament_cooling_before_tower[idx] = m_print->config().filament_cooling_before_tower.get_at(fi);
}
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
@@ -10337,8 +10366,8 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
// Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
// References are used so that we don't have to repeat the same code
for (int iter = 0; iter < 2; ++iter) {
const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters);
ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
const std::vector<const ExtrusionEntity*>& entities = (iter ? reg.infills : reg.perimeters);
std::vector<const ExtrusionEntity*>& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
const std::vector<const WipingExtrusions::ExtruderPerCopy*>& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides);
// Now the most important thing - which extrusion should we print.
@@ -10373,7 +10402,7 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder)
{
// We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
ExtrusionEntitiesPtr* perimeters_or_infills;
std::vector<const ExtrusionEntity*>* perimeters_or_infills;
std::vector<const WipingExtrusions::ExtruderPerCopy*>* perimeters_or_infills_overrides;
switch (type) {
@@ -10397,7 +10426,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
for (auto* ee : eec->entities)
perimeters_or_infills->emplace_back(ee);
} else
perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
perimeters_or_infills->emplace_back(eec);
if (copies_extruder != nullptr) {
// Don't reallocate overrides if not needed.