Merge branch 'main' into feat/plugin-feature

Resolve five conflicts, all of which needed both sides rather than a pick:

- BackgroundSlicingProcess: ours was a pure tabs->spaces reformat of base, so
  keep main's per-filament volume/nozzle map read-back (its only change here).
- GUI_App: main's #12506 else-if attached to an `if` this branch deleted;
  re-expressed onto the same-agent early-return path (the agent factory caches
  per id, so pointer equality is the same predicate).
- MainFrame: both sides relocated Sync Presets independently; keep main's
  push_notification plus the branch's Plugins menu items.
- Tab: the "TODO: Orca: Support hybrid" blocks were unchanged base, not a branch
  decision; take main's enabled Hybrid to match the already auto-merged siblings.
- test_config: union of both sides' cases (6 plugin + 9 multi-nozzle).
This commit is contained in:
SoftFever
2026-07-15 17:18:46 +08:00
986 changed files with 210610 additions and 18572 deletions

View File

@@ -1316,9 +1316,23 @@ int CLI::run(int argc, char **argv)
return CLI_INVALID_PARAMS;
}
BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl;
std::string temp_path = wxFileName::GetTempDir().utf8_str().data();
std::string temp_path = per_user_temp_dir(wxFileName::GetTempDir().utf8_str().data(), per_user_temp_id());
// Some consumers write into the temp root directly, so create it up front.
try {
boost::filesystem::create_directories(temp_path);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(warning) << "failed to create per-user temp dir " << temp_path << ": " << ex.what();
}
set_temporary_dir(temp_path);
// The Filament Track Switch flags are live-device state with no meaning in headless slicing;
// default both off unless explicitly provided on the command line, so an old 3MF that had
// them enabled still slices without the switch behavior.
if (!m_extra_config.has("has_filament_switcher"))
m_extra_config.set_key_value("has_filament_switcher", new ConfigOptionBool(false));
if (!m_extra_config.has("enable_filament_dynamic_map"))
m_extra_config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(false));
m_extra_config.apply(m_config, true);
m_extra_config.normalize_fdm();
@@ -1696,11 +1710,13 @@ int CLI::run(int argc, char **argv)
const Vec3d &instance_offset = model_instance->get_offset();
BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl;
}*/
current_printer_name = config.option<ConfigOptionString>("printer_settings_id")->value;
current_process_name = config.option<ConfigOptionString>("print_settings_id")->value;
// Read defensively — a 3mf missing preset ids (e.g. one produced
// by a non-GUI writer) would otherwise crash here on the deref.
if (const auto *o = config.option<ConfigOptionString>("printer_settings_id")) current_printer_name = o->value;
if (const auto *o = config.option<ConfigOptionString>("print_settings_id")) current_process_name = o->value;
current_printer_model = config.option<ConfigOptionString>("printer_model", true)->value;
current_filaments_name = config.option<ConfigOptionStrings>("filament_settings_id")->values;
current_extruder_count = config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
if (const auto *o = config.option<ConfigOptionStrings>("filament_settings_id")) current_filaments_name = o->values;
if (const auto *o = config.option<ConfigOptionFloats>("nozzle_diameter")) current_extruder_count = o->values.size();
current_printer_variant_count = config.option<ConfigOptionStrings>("printer_extruder_variant", true)->values.size();
current_print_variant_count = config.option<ConfigOptionStrings>("print_extruder_variant", true)->values.size();
current_is_multi_extruder = current_extruder_count > 1;
@@ -5930,6 +5946,72 @@ int CLI::run(int argc, char **argv)
else
filament_maps = part_plate->get_real_filament_maps(m_print_config);
// Multi-nozzle printers need the per-filament volume assignment as a grouping
// input in the manual modes: synthesize it from the per-extruder flow types when
// the caller did not provide one (an extruder whose nozzle stats span several
// volume types keeps the per-filament choice), and require explicit maps in
// nozzle-manual mode.
auto max_nozzle_counts_opt = m_print_config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate.
bool support_multi_nozzle =
max_nozzle_counts_opt &&
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
if (support_multi_nozzle && (mode == fmmManual || mode == fmmNozzleManual) && (plate_to_slice != 0)) {
// Orca: the grouping result is reconstructed purely from the passed maps in
// nozzle-manual mode, so all of them must be present (there are no separate
// per-nozzle CLI parameters to rebuild them from).
if (mode == FilamentMapMode::fmmNozzleManual &&
(!m_extra_config.has("filament_volume_map") || !m_extra_config.has("filament_nozzle_map") ||
!m_extra_config.has("filament_map"))) {
BOOST_LOG_TRIVIAL(error)
<< boost::format("%1%, can not find filament_volume_map/filament_nozzle_map/filament_map under Nozzle Manual mode") % __LINE__;
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
flush_and_exit(CLI_INVALID_PARAMS);
}
if (mode == fmmManual) {
// Build the volume map when absent: filaments on a single-volume extruder
// print with that extruder's volume; a mixed-volume extruder keeps the
// per-filament choice (default Standard).
std::vector<NozzleVolumeType> using_nozzle_volume_type = new_nozzle_volume_type;
using_nozzle_volume_type.resize(new_extruder_count, nvtStandard);
if (auto extruder_nozzle_stats_opt = m_print_config.option<ConfigOptionStrings>("extruder_nozzle_stats")) {
auto nozzle_stats = get_extruder_nozzle_stats(extruder_nozzle_stats_opt->values);
for (int e_index = 0; e_index < new_extruder_count && e_index < (int) nozzle_stats.size(); e_index++) {
if (nozzle_stats[e_index].size() > 1) {
using_nozzle_volume_type[e_index] = nvtHybrid;
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : extruder %2%, set nozzle_volume_type to hybrid ") % __LINE__ % (e_index + 1);
}
}
}
std::vector<int> &manual_volume_maps = m_extra_config.option<ConfigOptionInts>("filament_volume_map", true)->values;
// default to standard flow
manual_volume_maps.resize(filament_count, (int) (nvtStandard));
for (int f_index = 0; f_index < filament_count && f_index < (int) filament_maps.size(); f_index++) {
int f_extruder_index = filament_maps[f_index] - 1;
if (f_extruder_index >= 0 && f_extruder_index < new_extruder_count &&
using_nozzle_volume_type[f_extruder_index] != nvtHybrid) {
manual_volume_maps[f_index] = int(using_nozzle_volume_type[f_extruder_index]);
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : filament %2% extruder %3%, set filament_volume_map to %4% ") % __LINE__ % (f_index + 1) % (f_extruder_index + 1) % manual_volume_maps[f_index];
}
}
}
if (m_extra_config.has("filament_volume_map")) {
part_plate->set_filament_volume_maps(m_extra_config.option<ConfigOptionInts>("filament_volume_map")->values);
}
if (m_extra_config.has("filament_nozzle_map")) {
part_plate->set_filament_nozzle_maps(m_extra_config.option<ConfigOptionInts>("filament_nozzle_map")->values);
}
}
else if (!support_multi_nozzle && (mode == fmmNozzleManual)) {
BOOST_LOG_TRIVIAL(error)
<< boost::format("%1%, Nozzle Manual mode not supported for %2%") % __LINE__ % new_printer_name;
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
flush_and_exit(CLI_INVALID_PARAMS);
}
for (int index = 0; index < filament_maps.size(); index++)
{
int filament_extruder = filament_maps[index];
@@ -6168,6 +6250,22 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << time_using_cache << " secs.";
}
if (printer_technology == ptFFF) {
// Read the engine's final grouping back onto the plate so an exported
// project (--export-3mf / gcode.3mf) carries the concrete maps in its
// plate settings, matching what a GUI slice persists.
// Orca: deliberately gated to multi-extruder printers so single-extruder
// exports keep their plate settings unchanged.
if (new_extruder_count > 1) {
FilamentMapMode current_map_mode = print_fff->config().filament_map_mode.value;
if (is_auto_filament_map_mode(current_map_mode)) {
part_plate->set_filament_maps(print_fff->get_filament_maps());
part_plate->set_filament_volume_maps(print_fff->get_filament_volume_maps());
}
if (current_map_mode != FilamentMapMode::fmmNozzleManual) {
part_plate->set_filament_nozzle_maps(print_fff->get_filament_nozzle_maps());
}
}
std::string conflict_result = print_fff->get_conflict_string();
if (!conflict_result.empty()) {
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl;
@@ -7352,7 +7450,7 @@ bool CLI::export_project(Model *model, std::string& path, PlateDataPtrs &partpla
bool success = false;
StoreParams store_params;
store_params.path = path.c_str();
store_params.path = path;
store_params.model = model;
store_params.plate_data_list = partplate_data;
store_params.project_presets = project_presets;