Add Flashforge Adventurer 5 series local send workflow with IFS mapping (#12991)

* Add Flashforge AD5X local send dialog, IFS mapping, and LAN discovery

* Refine Flashforge AD5X IFS dialog behavior

* Refine Flashforge IFS slot selection dialog

* Fix Flashforge printer selection and print mapping

* Use 3MF for Flashforge local uploads

* Generalize Flashforge local API handling

* Handle Flashforge local API IFS support more robustly

* Use selected plate filament info for Flashforge IFS mapping

* Fix Flashforge current-plate mapping and widget sizing

* Improve Flashforge IFS contrast and color matching

* Fix Flashforge legacy plate export and upload naming

Resolve PLATE_CURRENT_IDX before the legacy send-to-printhost path calls send_gcode so single-plate Flashforge 3MF exports target the selected plate instead of leaking the sentinel into export_3mf.

Sanitize Flashforge upload names in one shared utility reused by both the dialog and the backend client. This keeps the UI-visible filename and the actual uploaded filename consistent and replaces printer-problematic characters such as '=' without scattering Flashforge-specific logic through the generic Plater flow.

* Keep Flashforge upload filename sanitization in the backend only

Drop the PrintHostSendDialog API changes and keep filename sanitization inside the Flashforge backend paths that actually talk to the printer. This keeps the generic send dialog flow untouched while still normalizing problematic upload names for both serial and local API uploads.

* Only use the Flashforge IFS dialog for local API uploads

* Use reported Flashforge IFS support without model fallback

* Remove unused Flashforge slot uniqueness tracking

* Include <array> for Flashforge discovery message
This commit is contained in:
Mariano Dupont
2026-05-27 12:03:44 -03:00
committed by GitHub
parent 9c63aee9f8
commit f118b6b337
9 changed files with 1626 additions and 25 deletions

View File

@@ -16081,6 +16081,18 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
if (upload_job.empty())
return;
const auto host_type_opt = physical_printer_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htElegooLink;
const auto* ff_serial_opt = physical_printer_config->option<ConfigOptionString>("flashforge_serial_number");
const auto* ff_code_opt = physical_printer_config->option<ConfigOptionString>("printhost_apikey");
const bool flashforge_local_api =
host_type == htFlashforge &&
ff_serial_opt != nullptr && !ff_serial_opt->value.empty() &&
ff_code_opt != nullptr && !ff_code_opt->value.empty();
if (flashforge_local_api)
use_3mf = true;
upload_job.upload_data.use_3mf = use_3mf;
// Obtain default output path
@@ -16128,8 +16140,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
{
auto preset_bundle = wxGetApp().preset_bundle;
const auto opt = physical_printer_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
const auto host_type = opt != nullptr ? opt->value : htElegooLink;
auto config = get_app_config();
std::unique_ptr<PrintHostSendDialog> pDlg;
@@ -16137,6 +16147,77 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
pDlg = std::make_unique<ElegooPrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,
storage_paths, storage_names,
config->get_bool("open_device_tab_post_upload"));
} else if (flashforge_local_api) {
auto* flashforge_host = dynamic_cast<Flashforge*>(upload_job.printhost.get());
if (flashforge_host == nullptr) {
show_error(this, _L("Flashforge host is not available."), false);
return;
}
std::vector<FlashforgeMaterialSlot> slots;
bool supports_material_station = false;
{
wxBusyCursor wait;
wxString msg;
if (!flashforge_host->fetch_material_slots(slots, &supports_material_station, msg)) {
show_error(this, msg.empty() ? _L("Unable to log in to the Flashforge printer.") : msg, false);
return;
}
}
std::vector<FilamentInfo> project_filaments;
PlateDataPtrs plate_data_list;
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config();
const auto* filament_color = dynamic_cast<const ConfigOptionStrings*>(cfg.option("filament_colour"));
const auto* filament_id_opt = dynamic_cast<const ConfigOptionStrings*>(cfg.option("filament_ids"));
const int resolved_plate_idx = plate_idx == PLATE_CURRENT_IDX ? get_partplate_list().get_curr_plate_index() : plate_idx;
auto enrich_project_filaments = [&](std::vector<FilamentInfo>& filaments) {
for (auto& filament : filaments) {
if (filament.id < 0)
continue;
std::string display_filament_type;
try {
filament.type = cfg.get_filament_type(display_filament_type, filament.id);
} catch (...) {
}
if (filament.type.empty())
filament.type = display_filament_type;
if (filament.type.empty())
filament.type = "Unknown";
filament.filament_id = filament_id_opt ? filament_id_opt->get_at(static_cast<size_t>(filament.id)) : "";
filament.color = filament_color ? filament_color->get_at(static_cast<size_t>(filament.id)) : "#FFFFFF";
if (filament.color.empty())
filament.color = "#FFFFFF";
}
};
p->partplate_list.store_to_3mf_structure(plate_data_list, true, plate_idx);
PlateData* selected_plate_data = (resolved_plate_idx >= 0 && resolved_plate_idx < static_cast<int>(plate_data_list.size())) ? plate_data_list[resolved_plate_idx] : nullptr;
if (selected_plate_data == nullptr && !plate_data_list.empty())
selected_plate_data = plate_data_list.front();
if (selected_plate_data != nullptr)
project_filaments = selected_plate_data->slice_filaments_info;
if (project_filaments.empty()) {
if (PartPlate* plate = get_partplate_list().get_plate(resolved_plate_idx); plate != nullptr)
project_filaments = plate->get_slice_filaments_info();
}
if (!project_filaments.empty())
enrich_project_filaments(project_filaments);
release_PlateData_list(plate_data_list);
pDlg = std::make_unique<FlashforgePrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,
storage_paths, storage_names,
config->get_bool("open_device_tab_post_upload"),
flashforge_host,
supports_material_station,
std::move(slots),
project_filaments);
} else {
pDlg = std::make_unique<PrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,
storage_paths, storage_names, config->get_bool("open_device_tab_post_upload"));
@@ -16166,7 +16247,8 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
if (use_3mf) {
// Process gcode
const int result = send_gcode(plate_idx, nullptr);
const int export_plate_idx = plate_idx == PLATE_CURRENT_IDX ? get_partplate_list().get_curr_plate_index() : plate_idx;
const int result = send_gcode(export_plate_idx, nullptr);
if (result < 0) {
wxString msg = _L("Abnormal print file data. Please slice again");