feat: Support 3MF as g-code (use_3mf option) (#14238)

* feat: add support for 3MF file format in printer configurations and export options

* fix file extension

* enable 3mf for X Max 4

* disable use_3mf for X Plus 4

* Fixed an issue where `label_object_enabled` was not properly propagated to 3mf

* enable exclude object for Max 4

* remove hardcoded use 3mf for flashforge, move them to the new printer profiles config
This commit is contained in:
SoftFever
2026-06-17 15:39:40 +08:00
committed by GitHub
parent 7ab3174f7c
commit 9eeb73b68b
26 changed files with 137 additions and 31 deletions

View File

@@ -2026,6 +2026,26 @@ wxBoxSizer* MainFrame::create_side_tools()
});
p->append_button(send_gcode_btn);
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
// also offer exporting the sliced .gcode.3mf bundle
const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
const auto* use_3mf_opt = printer_config.option<ConfigOptionBool>("use_3mf");
if (use_3mf_opt != nullptr && use_3mf_opt->value) {
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
export_sliced_file_btn->SetCornerRadius(0);
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(export_sliced_file_btn);
}
p->append_button(export_gcode_btn);
}
else {
@@ -4033,6 +4053,13 @@ void MainFrame::set_print_button_to_default(PrintSelectType select_type)
m_print_enable = get_enable_print_status() && can_send_gcode();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else if (select_type == PrintSelectType::eExportSlicedFile) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
if (m_print_enable)
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else {
// unsupport
return;

View File

@@ -2474,7 +2474,11 @@ void Sidebar::update_all_preset_comboboxes()
else
p->m_bpButton_ams_filament->Hide();
auto print_btn_type = MainFrame::PrintSelectType::eExportGcode;
// Orca: with "Support 3MF as gcode" (use_3mf) the local export is a .gcode.3mf bundle, so when no
// printer host/IP is configured the default action is "Export plate sliced file" (mirrors the
// print dropdown) instead of "Export G-code file".
auto print_btn_type = cfg.opt_bool("use_3mf") ? MainFrame::PrintSelectType::eExportSlicedFile
: MainFrame::PrintSelectType::eExportGcode;
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
if(url.empty())
@@ -10229,7 +10233,7 @@ void Plater::priv::on_action_print_plate(SimpleEvent&)
m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index());
m_select_machine_dlg->ShowModal();
} else {
q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr, true);
q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr);
}
}
@@ -10330,7 +10334,7 @@ void Plater::priv::on_action_print_all(SimpleEvent&)
m_select_machine_dlg->prepare(PLATE_ALL_IDX);
m_select_machine_dlg->ShowModal();
} else {
q->send_gcode_legacy(PLATE_ALL_IDX, nullptr, true);
q->send_gcode_legacy(PLATE_ALL_IDX, nullptr);
}
}
@@ -16093,7 +16097,7 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &
// and let the background processing start.
this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
}
void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool use_3mf)
void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn)
{
// if physical_printer is selected, send gcode for this printer
// DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
@@ -16105,17 +16109,9 @@ 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;
// Orca: the use_3mf printer option makes us send a .gcode.3mf to the printer
const auto* use_3mf_opt = physical_printer_config->option<ConfigOptionBool>("use_3mf");
const bool use_3mf = use_3mf_opt != nullptr && use_3mf_opt->value;
upload_job.upload_data.use_3mf = use_3mf;
@@ -16139,7 +16135,8 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
if (use_3mf) {
default_output_file.replace_extension("3mf");
// Orca: a gcode-in-3mf bundle is named ".gcode.3mf" (matching "Export plate sliced file")
default_output_file.replace_extension(".gcode.3mf");
}
// Repetier specific: Query the server for the list of file groups.
@@ -16166,6 +16163,13 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
auto preset_bundle = wxGetApp().preset_bundle;
auto config = get_app_config();
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();
std::unique_ptr<PrintHostSendDialog> pDlg;
if (host_type == htElegooLink) {
pDlg = std::make_unique<ElegooPrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,

View File

@@ -522,7 +522,7 @@ public:
/* -1: send current gcode if not specified
* -2: send all gcode to target machine */
int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr, bool use_3mf = false);
void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
//BBS jump to nonitor after print job finished
void send_calibration_job_finished(wxCommandEvent &evt);

View File

@@ -4477,6 +4477,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
optgroup->append_single_option_line("use_3mf");
optgroup->append_single_option_line("scan_first_layer" , "printer_basic_information_advanced#scan-first-layer");
optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery");
//option = optgroup->get_option("wrapping_exclude_area");