Merge upstream/main into belt-printer

Brings belt-printer up to main 4b4a261787. Resolutions:

- G-code header (#15897, #15915): main moved the header, config and
  thumbnail block later in _do_export; write_belt_header() moves with it,
  still after the thumbnails and outside the BTT_TFT gate.
- _extrude: first-layer acceleration keeps the per-path first-layer plane
  test with main's cached nozzle index (#16028); main's set_speed out-param
  form (#16108) everywhere else.
- GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings
  that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy,
  which is the overload GCode calls, and appends to the caller's string.
- GCodeProcessorResult: the belt fields join main's forwarding assign.
- Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree
  support join types lose their ClipperLib qualifier.
- CLI arrange (#15837): belt printers still reserve no wipe tower.
- Wipe tower options (#15841): the two new sparse-layer toggles are hidden
  for belt printers like the rest of the tower options.
- Keyboard shortcuts (#15706): main's registry replaces the old key switch;
  the belt view toggle is re-registered in the next commit.
- Print::process: the belt purge-plan undo runs before main's SliceStarted
  event.
- scripts/filament_id_snapshot.json: deleted on main (a77209af8f).
- Includes and appended tests: union of both sides.
This commit is contained in:
harrierpigeon
2026-10-04 19:20:33 -05:00
11471 changed files with 2992690 additions and 2720441 deletions
+384 -34
View File
@@ -1,4 +1,7 @@
#include "PrintConfig.hpp"
#include "CommonDefs.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "PrintConfigConstants.hpp"
#include "BeltTransform.hpp"
#include "ClipperUtils.hpp"
@@ -7,9 +10,28 @@
#include "FilamentMixer.hpp"
#include "MaterialType.hpp"
#include "I18N.hpp"
#include "enum_bitmask.hpp"
#include "format.hpp"
#include "GCode/Thumbnails.hpp"
#include <numeric>
#include <cstddef>
#include <algorithm>
#include <cassert>
#include <map>
#include <boost/algorithm/string/classification.hpp>
#include <iterator>
#include <cstdlib>
#include "libslic3r.h"
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
#include <boost/algorithm/string/constants.hpp>
#include <limits>
#include <memory>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/to_seq.hpp>
#include <cstdint>
#include <set>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
@@ -20,6 +42,11 @@
#include <boost/log/trivial.hpp>
#include <boost/thread.hpp>
#include <float.h>
#include <string>
#include <vector>
#include <utility>
#include <sstream>
#include <unordered_map>
namespace {
std::set<std::string> SplitStringAndRemoveDuplicateElement(const std::string &str, const std::string &separator)
@@ -154,6 +181,7 @@ static t_config_enum_values s_keys_map_PrintHostType {
{ "octoprint", htOctoPrint },
{ "crealityprint", htCrealityPrint },
{ "duet", htDuet },
{ "ultimaker", htUltiMaker },
{ "flashair", htFlashAir },
{ "astrobox", htAstroBox },
{ "repetier", htRepetier },
@@ -674,7 +702,9 @@ static const t_config_enum_values s_keys_map_NozzleVolumeType = {
{ "Standard", nvtStandard },
{ "High Flow", nvtHighFlow },
{ "TPU High Flow", nvtTPUHighFlow },
{ "Hybrid", nvtHybrid }
{ "Hybrid", nvtHybrid },
{ "E3D High Flow", nvtE3DHighFlow },
{ "Extra High Flow", nvtExtraHighFlow }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType)
@@ -717,14 +747,66 @@ std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolume
return variant_string;
}
int find_variant_index(const std::string& variant, int variant_id_1based, const std::vector<std::string>& variant_list, const std::vector<int>& variant_ids_1based)
{
const int count = int(variant_list.empty() ? variant_ids_1based.size() : variant_list.size());
if (count == 0)
return 0;
auto same_id = [&](int index) {
return variant_id_1based < 0 || variant_ids_1based.empty() || (index < int(variant_ids_1based.size()) && variant_ids_1based[index] == variant_id_1based);
};
for (int index = 0; index < int(variant_list.size()); ++index)
if (variant_list[index] == variant && same_id(index))
return index;
// Without this variant, use the id's own first variant (usually Standard), not variant index 0,
// which belongs to the first filament or extruder.
for (int index = 0; index < count; ++index)
if (same_id(index))
return index;
return -1;
}
std::vector<int> map_variant_indices(const std::vector<std::string>& variants, const std::vector<int>& ids,
const std::vector<std::string>& from_variants, const std::vector<int>& from_ids)
{
const size_t count = variants.empty() ? ids.size() : variants.size();
std::vector<int> variant_index(count);
for (size_t index = 0; index < count; ++index) {
if (!ids.empty() && index >= ids.size()) {
variant_index[index] = -1;
continue;
}
variant_index[index] = find_variant_index(index < variants.size() ? variants[index] : std::string(),
ids.empty() ? -1 : ids[index], from_variants, from_ids);
}
return variant_index;
}
int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector<std::string>& variant_list, const std::vector<int>& variant_ids_1based)
{
assert(variant_list.size() == variant_ids_1based.size());
std::string extruder_variant = get_extruder_variant_string(extruder_type, volume_type);
for (int index = 0; index < int(variant_list.size()); ++index) {
if (extruder_variant == variant_list[index] && variant_ids_1based[index] == variant_id_1based) { return index; }
const int index = find_variant_index(get_extruder_variant_string(extruder_type, volume_type), variant_id_1based, variant_list, variant_ids_1based);
return std::max(index, 0);
}
std::set<NozzleVolumeType> get_extruder_supported_nozzle_volume_types(const DynamicPrintConfig &printer_config, int extruder_id)
{
std::set<NozzleVolumeType> supported_types;
auto *variant_list = printer_config.option<ConfigOptionStrings>("extruder_variant_list");
auto *extruder_types = printer_config.option<ConfigOptionEnumsGeneric>("extruder_type");
if (!variant_list || !extruder_types || extruder_id < 0 ||
extruder_id >= (int) variant_list->values.size() || extruder_id >= (int) extruder_types->values.size())
return supported_types;
const ExtruderType extruder_type = ExtruderType(extruder_types->values[extruder_id]);
for (NozzleVolumeType volume_type : get_valid_nozzle_volume_type()) {
// An unsupported extruder type yields an empty name, which would match any list.
const std::string variant = get_extruder_variant_string(extruder_type, volume_type);
if (!variant.empty() && variant_list->values[extruder_id].find(variant) != std::string::npos)
supported_types.insert(volume_type);
}
return 0;
return supported_types;
}
std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type)
@@ -1570,7 +1652,7 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add("top_solid_infill_flow_ratio", coFloat);
def = this->add("top_solid_infill_flow_ratio", coFloats);
def->label = L("Top surface flow ratio");
def->category = L("Advanced");
def->tooltip = L("This factor affects the amount of material for top solid infill. "
@@ -1579,7 +1661,8 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->max = 2;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1));
def->nullable = true;
def->set_default_value(new ConfigOptionFloatsNullable{1});
def = this->add("bottom_solid_infill_flow_ratio", coFloat);
def->label = L("Bottom surface flow ratio");
@@ -2639,6 +2722,16 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("5");
def->mode = comAdvanced;
// Orca: already carried by the BBL/Qidi/Geeetech/Eryone machine profiles, which inherited it from
// the BambuStudio import; without a definition here it was parsed as an unknown key and dropped.
def = this->add("extruder_clearance_dist_to_rod", coFloat);
def->label = L("Distance to rod");
def->tooltip = L("Horizontal distance of the nozzle tip to the rod's farther edge. Used for collision avoidance in by-object printing.");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(40));
def = this->add("extruder_clearance_height_to_rod", coFloat);
def->label = L("Height to rod");
def->tooltip = L("Distance from the nozzle tip to the lower rod. Used for collision avoidance in by-object printing.");
@@ -5481,6 +5574,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("prusaconnect");
def->enum_values.push_back("octoprint");
def->enum_values.push_back("duet");
def->enum_values.push_back("ultimaker");
def->enum_values.push_back("flashair");
def->enum_values.push_back("astrobox");
def->enum_values.push_back("repetier");
@@ -5497,6 +5591,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("PrusaConnect");
def->enum_labels.push_back("Octo/Klipper");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("UltiMaker");
def->enum_labels.push_back("FlashAir");
def->enum_labels.push_back("AstroBox");
def->enum_labels.push_back("Repetier");
@@ -5627,6 +5722,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");
@@ -5989,15 +6094,20 @@ void PrintConfigDef::init_fff_params()
def->label = "Nozzle Volume Type";
def->tooltip = "Nozzle volume type for extruders.";
def->enum_keys_map = &ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
// Order must match the NozzleVolumeType enum values (Standard=0, High Flow=1, Hybrid=2, TPU High Flow=3).
// Listed in display order. A position is not the enum value (E3D High Flow is 5, after the reserved 4),
// so map a position to its NozzleVolumeType through enum_keys_map.
def->enum_values.push_back(L("Standard"));
def->enum_values.push_back(L("High Flow"));
def->enum_values.push_back(L("Hybrid"));
def->enum_values.push_back(L("TPU High Flow"));
def->enum_values.push_back(L("E3D High Flow"));
def->enum_values.push_back(L("Extra High Flow"));
def->enum_labels.push_back(L("Standard"));
def->enum_labels.push_back(L("High Flow"));
def->enum_labels.push_back(L("Hybrid"));
def->enum_labels.push_back(L("TPU High Flow"));
def->enum_labels.push_back(L("E3D High Flow"));
def->enum_labels.push_back(L("Extra High Flow"));
def->mode = comSimple;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -6010,10 +6120,14 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back(L("High Flow"));
def->enum_values.push_back(L("Hybrid"));
def->enum_values.push_back(L("TPU High Flow"));
def->enum_values.push_back(L("E3D High Flow"));
def->enum_values.push_back(L("Extra High Flow"));
def->enum_labels.push_back(L("Standard"));
def->enum_labels.push_back(L("High Flow"));
def->enum_labels.push_back(L("Hybrid"));
def->enum_labels.push_back(L("TPU High Flow"));
def->enum_labels.push_back(L("E3D High Flow"));
def->enum_labels.push_back(L("Extra High Flow"));
def->mode = comDevelop;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -6052,7 +6166,7 @@ void PrintConfigDef::init_fff_params()
// Per-nozzle volume type. Forward-compat-only registration with no slicing consumer — nothing in
// src/ reads it; the engine resolves per-nozzle volume types from `extruder_nozzle_stats` tokens
// instead. Kept registered so a project/config carrying it loads without an unknown-option
// substitution warning. Registers Standard/High Flow/TPU High Flow only (no Hybrid).
// substitution warning. Registers the physical types only (no Hybrid).
// Internal use only, no translation.
def = this->add("extruder_nozzle_volume_type", coEnums);
def->label = "Extruder nozzle volume type";
@@ -6061,9 +6175,13 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("Standard");
def->enum_values.push_back("High Flow");
def->enum_values.push_back("TPU High Flow");
def->enum_values.push_back("E3D High Flow");
def->enum_values.push_back("Extra High Flow");
def->enum_labels.push_back("Standard");
def->enum_labels.push_back("High Flow");
def->enum_labels.push_back("TPU High Flow");
def->enum_labels.push_back("E3D High Flow");
def->enum_labels.push_back("Extra High Flow");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -6357,6 +6475,36 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wipe_inward", coBool);
def->label = L("Wipe inward");
def->category = L("Quality");
def->tooltip = L("Applies only to external walls, including hole boundaries. Moves the hot nozzle toward printed "
"inner walls during wiping to reduce reheating of freshly printed plastic and seam marks.\n\n"
"Especially useful at layer heights below 0.1 mm, where wipe marks are more visible.\n\n"
"Uses the regular wipe if no adjacent inner wall is already printed (single-wall areas or "
"Outer/Inner wall order), or if no supported inward path can be found, for example at tight "
"corners or seam gaps.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wipe_inward_distance", coFloatOrPercent);
def->label = L("Wipe inward distance");
def->category = L("Quality");
// xgettext:no-c-format, no-boost-format
def->tooltip = L("The distance the wipe path is shifted away from the external perimeter, specified in millimeters "
"or as a percentage of the actual outer-wall extrusion width.\n\n"
"For example, 50% shifts the path by half of the outer-wall width. The effective offset is limited "
"by both the actual outer-wall width and the available spacing to the adjacent wall, so values "
"above 100% or an equivalent absolute distance have no additional effect. "
"Set to 0 to disable the offset.");
def->sidetext = L("mm or %");
def->ratio_over = "outer_wall_line_width";
def->min = 0;
def->max = 100;
def->max_literal = 2; // Orca: G-code generation also clamps literal values to the actual outer-wall width.
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(50, true));
def = this->add("wipe_before_external_loop", coBool);
def->label = L("Wipe before external loop");
def->category = L("Quality");
@@ -6647,7 +6795,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("G-code written at the very top of the output file, before any other content. "
"Useful for adding metadata that printer firmware reads from the first lines of the file "
"(e.g. estimated print time, filament usage). "
"Supports placeholders like {print_time_sec} and {used_filament_length}.");
"Supports placeholders like {print_time_total_sec}, {print_time_day}, {print_time_hour}, {print_time_minute}, {print_time_sec} and {used_filament_length}.");
def->multiline = true;
def->full_width = true;
def->height = 8;
@@ -6734,8 +6882,24 @@ void PrintConfigDef::init_fff_params()
def = this->add("wipe_tower_no_sparse_layers", coBool);
def->label = L("No sparse layers (beta)");
def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no tool changes. "
"On layers with a tool change, extruder will travel downward to print the wipe tower. "
"User is responsible for ensuring there is no collision with the print.");
"On layers with a tool change, extruder will travel downward to print the wipe tower, "
"so the tower ends up below the model and the toolhead has to reach down to it. "
"Layouts where that would collide with an already printed object are rejected. "
"Has no effect with smooth timelapse or clumping detection, which need a tower on every layer.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wipe_tower_sparse_layers_combination", coBool);
def->label = L("Combine sparse layers");
def->tooltip = L("If enabled, consecutive layers on which the prime tower has no filament change are printed as a single "
"thicker tower layer instead of one thin layer each, the same way infill combination merges sparse infill. "
"The merged layer is printed at the top of the run, at the height of everything it covers.\n\n"
"Only whole layers are merged, and never past the maximum layer height of the nozzle printing the tower "
"(three quarters of the nozzle diameter when that is left at 0). Two or more layers therefore have to fit "
"under that limit before anything changes at all: at a 0.2 mm layer height under a 0.3 mm maximum nothing "
"is merged, while at 0.1 mm three layers become one.\n\n"
"Unlike \"No sparse layers\" the tower keeps following the model, so the toolhead never has to reach down to it. "
"Has no effect with \"No sparse layers\", smooth timelapse or clumping detection, which need a tower on every layer.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
@@ -6761,6 +6925,34 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.emplace_back(L("Cyclic"));
def->set_default_value(new ConfigOptionEnum<ToolChangeOrderingType>(ToolChangeOrderingType::Default));
def = this->add("toolchange_cyclic_order", coString);
def->label = L("Cyclic order");
def->category = L("Advanced");
def->tooltip = L(
"Custom filament sequence used by the cyclic toolchange ordering, as filament numbers separated by commas (e.g. \"3,2,1,4\").\n"
"Each layer prints its filaments following this sequence; filaments not listed are printed last, in ascending order.\n"
"Leave empty to cycle through the filaments in ascending order."
);
def->mode = comExpert;
def->set_default_value(new ConfigOptionString(""));
def = this->add("toolchange_cyclic_first_layer", coBool);
def->label = L("Apply cyclic order to first layer");
def->category = L("Advanced");
def->tooltip = L(
"Applies the cyclic toolchange order to the first layer as well.\n"
"By default this is disabled, because the first layer is instead ordered for the best bed "
"adhesion: filaments that print small, fragile first-layer features are printed last, so the "
"following tool changes and travel moves are less likely to knock those weakly anchored parts "
"loose. This first-layer order also honors a custom first layer filament sequence when one is set. "
"The cyclic order's benefit (extra tool changes give each layer more time to cool) does not apply "
"to the first layer, which is printed slowly and hot for adhesion.\n"
"Enable this only if you need the exact same tool sequence on every layer, including the first, at "
"the cost of that adhesion optimization."
);
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("slice_closing_radius", coFloat);
def->label = L("Slice gap closing radius");
def->category = L("Quality");
@@ -6773,7 +6965,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("slicing_mode", coEnum);
def->label = L("Slicing Mode");
def->category = L("Other");
def->category = L("Others");
def->tooltip = L("Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model.");
def->enum_keys_map = &ConfigOptionEnum<SlicingMode>::get_enum_values();
def->enum_values.push_back("regular");
@@ -9438,6 +9630,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
value = "tree(auto)";
} else if (opt_key == "support_base_pattern" && value == "none") {
value = "hollow";
} else if (opt_key == "tree_support_wall_count" && value == "-1") {
value = "0";
} else if (opt_key == "different_settings_to_system") {
std::string copy_value = value;
copy_value.erase(std::remove(copy_value.begin(), copy_value.end(), '\"'), copy_value.end()); // remove '"' in string
@@ -9657,6 +9851,10 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
}
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
}
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
}
const PrintConfigDef print_config_def;
@@ -9706,7 +9904,8 @@ std::set<std::string> print_options_with_variant = {
"initial_layer_travel_jerk",
"default_junction_deviation",
"print_extruder_id", //coInts
"print_extruder_variant" //coStrings
"print_extruder_variant", //coStrings
"top_solid_infill_flow_ratio"
};
std::set<std::string> filament_options_with_variant = {
@@ -9758,6 +9957,23 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing",
"filament_ironing_inset",
"filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
// Orca: cooling fans, multi-tool ramming and recommended nozzle temperature range
"fan_min_speed",
"fan_max_speed",
"additional_cooling_fan_speed",
"filament_minimal_purge_on_wipe_tower",
"filament_multitool_ramming",
"filament_multitool_ramming_volume",
"filament_multitool_ramming_flow",
"nozzle_temperature_range_low",
"nozzle_temperature_range_high",
"activate_air_filtration",
"activate_air_filtration_during_print",
"activate_air_filtration_on_completion",
@@ -10363,6 +10579,13 @@ bool DynamicPrintConfig::is_using_different_extruders()
return ret;
}
bool DynamicPrintConfig::has_multi_variant_filament() const
{
auto variants = dynamic_cast<const ConfigOptionStrings*>(this->option("filament_extruder_variant"));
auto diameters = dynamic_cast<const ConfigOptionFloats*>(this->option("filament_diameter"));
return variants && diameters && variants->size() > diameters->size();
}
bool DynamicPrintConfig::support_different_extruders(int& extruder_count) const
{
std::set<std::string> variant_set;
@@ -10958,6 +11181,34 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride);
}
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
// The values are one per filament, without variant strings, or a single value for all of them. The
// variant strings do not change today's mapping; they are passed so a rule that reads them applies here too.
const auto *variants = config.option<ConfigOptionStrings>("filament_extruder_variant");
const std::vector<std::string> variant_list = variants && variants->size() == self_index->size() ? variants->values : std::vector<std::string>();
std::vector<int> filament_ids(filament_count);
std::iota(filament_ids.begin(), filament_ids.end(), 1);
const std::vector<int> from_filaments = map_variant_indices(variant_list, self_index->values, {}, filament_ids);
const std::vector<int> from_single = map_variant_indices(variant_list, self_index->values, {}, {});
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
const std::vector<int> &variant_index = opt->size() == size_t(filament_count) ? from_filaments : from_single;
std::unique_ptr<ConfigOption> source(opt->clone());
// -1 and a single-value source both resolve to the first value through get_at()
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(source.get(), variant, variant_index[variant]);
}
}
//used for object/region config
//use the smallest of multiple to single
@@ -11363,6 +11614,11 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
return;
}
std::vector<int> filament_maps = opt_filament_map->values;
auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
// Orca: a map shorter than the filament count must not drop the filaments past its end;
// they take the first extruder.
if (opt_ids && !opt_ids->values.empty())
filament_maps.resize(std::max<size_t>(filament_maps.size(), *std::max_element(opt_ids->values.begin(), opt_ids->values.end())), 1);
size_t filament_count = filament_maps.size();
//apply process settings
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("extruder_type"));
@@ -11381,7 +11637,6 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
// indexed out of bounds.
if (opt_filament_volume_maps && opt_filament_volume_maps->values.size() == filament_count)
filament_volume_maps = opt_filament_volume_maps->values;
auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
std::vector<int> variant_index;
variant_index.resize(filament_count, -1);
@@ -11398,9 +11653,10 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
//variant index
variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name);
if (variant_index[f_index] < 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%")
// Orca: a filament need not define every extruder variant (a Direct Drive filament on a
// Bowden printer), so this is not an invalid state: the filament's first variant is used.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%")
%__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %filament_maps[f_index];
assert(false);
//for some updates happens in a invalid state(caused by popup window)
//we need to avoid crash
variant_index[f_index] = 0;
@@ -11783,8 +12039,14 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
else
variant_index.resize(1, 0);
// A parent variant the child does not list (the parent gained it after the child was saved, or the
// child lists none) takes the child's first variant of the same extruder, as slicing does.
// Left unmatched, the parent's value would silently replace the user's.
if (target_variant_count == 0) {
variant_index[0] = 0;
// The child's one value belongs to the extruder of the parent's first variant.
if (cur_variant_count > 0)
variant_index = map_variant_indices(cur_extruder_variants, cur_extruder_ids, {},
cur_extruder_ids.empty() ? std::vector<int>() : std::vector<int>{cur_extruder_ids[0]});
}
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
//should not happen
@@ -11796,19 +12058,8 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%")
%extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size();
}
else {
for (int i = 0; i < cur_variant_count; i++)
{
for (int j = 0; j < target_variant_count; j++)
{
if ((cur_extruder_variants[i] == target_extruder_variants[j])
&&(cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j])))
{
variant_index[i] = j;
break;
}
}
}
else if (cur_variant_count > 0) {
variant_index = map_variant_indices(cur_extruder_variants, cur_extruder_ids, target_extruder_variants, target_extruder_ids);
}
const t_config_option_keys &keys = new_config.keys();
@@ -12169,6 +12420,21 @@ PRINT_CONFIG_CACHE_INITIALIZE((
SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig))
static int print_config_static_initialized = print_config_static_initializer();
// The same set() calls ConfigBase::apply_only() makes, without looking every key up by name. Out of line so the
// option list is expanded for this once, not in every file that includes PrintConfig.hpp.
#define PRINT_CONFIG_APPLY_TO_DEFINITION(r, data, CLASS_NAME) \
bool CLASS_NAME::apply_to(ConfigBase &target) const \
{ \
auto *dst = dynamic_cast<CLASS_NAME*>(&target); \
if (dst == nullptr) \
return false; \
visit_option_pairs(*dst, *this, [](const char*, ConfigOption &a, const ConfigOption &b) { a.set(&b); return true; }); \
return true; \
}
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_APPLY_TO_DEFINITION, _, (PrintObjectConfig)(PrintRegionConfig)(MachineEnvelopeConfig)(GCodeConfig)
(SLAMaterialConfig)(SLAPrintConfig)(SLAPrintObjectConfig)(SLAPrinterConfig))
#undef PRINT_CONFIG_APPLY_TO_DEFINITION
//BBS: remove unused command currently
CLIActionsConfigDef::CLIActionsConfigDef()
{
@@ -12288,6 +12554,19 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def->tooltip = L("Do not run any validity checks, such as G-code path conflicts check.");
def->set_default_value(new ConfigOptionBool(false));
// --strict turns the non-critical slicing warnings the CLI otherwise only logs into a
// failed run, and records strict_mode in result.json so consumers can tell the modes apart.
def = this->add("strict", coBool);
def->label = L("Strict mode");
def->tooltip = L("Exit non-zero when slicing raises a non-critical warning that is "
"otherwise only logged, such as a model that needs support while "
"support is disabled. Use this in CI or scripted pipelines that should "
"never ship a subtly broken slice. Each such warning is also listed "
"with a stable class in the `warnings` array of result.json, which is "
"written on Linux only. Cannot be combined with --no-check, which skips "
"the support check.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("normative_check", coBool);
def->label = L("Normative check");
def->tooltip = L("Check the normative items.");
@@ -12308,9 +12587,29 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def->tooltip = L("This outputs the model\u2019s information.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("inspect_mesh", coBool);
def->label = L("Inspect mesh (JSON to stdout)");
def->tooltip = L("Print a JSON summary of each loaded object to stdout, then exit: its bounding boxes and the "
"convex hull faces it can be laid on, with their normals, areas and centers. These are the faces "
"the --ground-* options choose from. Machine-readable alternative to --info.");
def->set_default_value(new ConfigOptionBool(false));
// --inspect-paint \u2014 dump the per-facet enforcer/blocker/extruder/fuzzy
// paint state stored on the loaded model (supports, seam, MMU color,
// fuzzy-skin) as JSON. Read-only; lets CI / scripted / AI tooling
// reason about existing paint on a .3mf without loading the GUI.
def = this->add("inspect_paint", coBool);
def->label = L("Inspect paint (JSON to stdout)");
def->tooltip = L("Print a structured JSON summary of every painted layer "
"(supports, seam, MMU color, fuzzy-skin) already stored on "
"the loaded model \u2014 per-state facet count, surface area, "
"and mesh-local bounding box \u2014 then exit. Machine-readable "
"alternative to opening the paint gizmos in the GUI.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("export_settings", coString);
def->label = L("Export Settings");
def->tooltip = L("This exports settings to a file.");
def->tooltip = L("This exports settings to a file. Use - to write them to stdout.");
def->cli_params = "settings.json";
def->set_default_value(new ConfigOptionString("output.json"));
@@ -12427,6 +12726,34 @@ CLITransformConfigDef::CLITransformConfigDef()
def->sidetext = u8"°"; // degrees, don't need translation
def->set_default_value(new ConfigOptionFloat(0));
// The --ground-* options choose from the faces the "Lay on Face" gizmo offers. Like the other
// transforms they run in command-line order, so they see the rotations given before them.
def = this->add("ground_largest_face", coBool);
def->label = L("Ground largest face");
def->tooltip = L("Lay each object on the largest face of its convex hull and drop it onto the bed. Of equally large "
"faces, the one already facing down is kept. Objects without a face large enough to rest on are left "
"as they are. Transforms run in command-line order, so rotations given before this option are respected. "
"--orient 1 runs after all transforms and replaces the orientation.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("ground_face_normal", coString);
def->label = L("Ground face by normal");
def->tooltip = L("Lay each object on the convex hull face whose outward normal is closest to the direction NX,NY,NZ "
"and drop it onto the bed. The direction is in object coordinates, which include the rotations given "
"before this option and match the plate axes unless the input file rotates the object. For example, "
"1,0,0 stands the object on its +X side. --orient 1 runs after all transforms and replaces the orientation.");
def->cli_params = "NX,NY,NZ";
def->set_default_value(new ConfigOptionString(""));
def = this->add("ground_face_point", coString);
def->label = L("Ground face at point");
def->tooltip = L("Lay each object on the convex hull face that contains the point X,Y,Z and drop it onto the bed. "
"The point is in object coordinates, which include the rotations given before this option; "
"--inspect-mesh reports face centers in them. Objects without such a face are left as they are, and "
"the run fails if no object has one. --orient 1 runs after all transforms and replaces the orientation.");
def->cli_params = "X,Y,Z";
def->set_default_value(new ConfigOptionString(""));
def = this->add("scale", coFloat);
def->label = L("Scale");
def->tooltip = L("Scale the model by a float factor.");
@@ -12597,6 +12924,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->tooltip = L("If enabled, Arrange will allow rotation when placing objects.");
def->set_default_value(new ConfigOptionBool(true));
def = this->add("align_to_y_axis", coBool);
def->label = L("Align to Y axis when arranging");
def->tooltip = L("If enabled, Arrange will turn each object so its long side runs along the Y axis before placing it. "
"When not given, it is on for i3 printers and off for the others, as in the GUI.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("avoid_extrusion_cali_region", coBool);
def->label = L("Avoid extrusion calibrate region when arranging");
def->tooltip = L("If enabled, Arrange will avoid extrusion calibrate region when placing objects.");
@@ -12739,6 +13072,7 @@ OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
new_def("curr_bed_type", coString, "Current bed type", "Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
}
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
@@ -12809,10 +13143,26 @@ PrintStatisticsConfigDef::PrintStatisticsConfigDef()
def->label = L("Used filament");
def->tooltip = L("Total length of filament used in the print.");
def = this->add("print_time_sec", coString);
def->label = L("Print time (seconds)");
def = this->add("print_time_total_sec", coString);
def->label = L("Print time (total seconds)");
def->tooltip = L("Total estimated print time in seconds. Replaced with actual value during post-processing.");
def = this->add("print_time_day", coString);
def->label = L("Print time (days component)");
def->tooltip = L("Estimated print time day component (normal mode). Replaced with actual value during post-processing.");
def = this->add("print_time_hour", coString);
def->label = L("Print time (hours component)");
def->tooltip = L("Estimated print time hour component (normal mode). Replaced with actual value during post-processing.");
def = this->add("print_time_minute", coString);
def->label = L("Print time (minutes component)");
def->tooltip = L("Estimated print time minute component (normal mode). Replaced with actual value during post-processing.");
def = this->add("print_time_sec", coString);
def->label = L("Print time (seconds component)");
def->tooltip = L("Estimated print time second component (normal mode). Replaced with actual value during post-processing.");
def = this->add("used_filament_length", coString);
def->label = L("Filament length (meters)");
def->tooltip = L("Total filament length used in meters. Replaced with actual value during post-processing.");