mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-03 12:04:05 -06:00
Add elegoo centauri carbon profile (#8405)
* Added Elegoolink connection * Set Elegoo CC default bed to btPTE * Friendly output of some error codes of PrintHost * feat: Add elegoo centauri carbon profile * fix: Fix the issue where the bed type in the printer configuration does not match the bed temperature settings when multiple bed types are not supported. * feat: Modify the elegoo process parameters to disable slowdown_for_curled_perimeters. * feat: Update comment to clarify plate visibility for multi bed support, BBL printer, and selected bed type. * fix: Optimize ElegooLink upload; The code is clearer than before. * feat: Format the ElegooPrintHostSendDialog code. * fix: Remove the unnecessary instantiation attribute in the Elegoo process. * fix: Flatpak compilation failed --------- Co-authored-by: anjis <anjis.zhou@elegoo.com>
This commit is contained in:
parent
dd2f8af68b
commit
00a3e78f8a
137 changed files with 5121 additions and 47 deletions
|
@ -567,6 +567,9 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Jobs/OAuthJob.hpp
|
||||
Utils/SimplyPrint.cpp
|
||||
Utils/SimplyPrint.hpp
|
||||
Utils/ElegooLink.hpp
|
||||
Utils/ElegooLink.cpp
|
||||
Utils/WebSocketClient.hpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -1307,8 +1307,9 @@ void Sidebar::update_all_preset_comboboxes()
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Orca: combobox don't have the btDefault option, so we need to -1
|
||||
m_bed_type_list->SelectAndNotify(btPEI - 1);
|
||||
// m_bed_type_list->SelectAndNotify(btPEI - 1);
|
||||
BedType bed_type = preset_bundle.printers.get_edited_preset().get_default_bed_type(&preset_bundle);
|
||||
m_bed_type_list->SelectAndNotify((int) bed_type - 1);
|
||||
m_bed_type_list->Disable();
|
||||
}
|
||||
|
||||
|
@ -12660,38 +12661,58 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
|
|||
}
|
||||
}
|
||||
|
||||
auto config = get_app_config();
|
||||
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names, config->get_bool("open_device_tab_post_upload"));
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
config->set_bool("open_device_tab_post_upload", dlg.switch_to_device_tab());
|
||||
upload_job.switch_to_device_tab = dlg.switch_to_device_tab();
|
||||
upload_job.upload_data.upload_path = dlg.filename();
|
||||
upload_job.upload_data.post_action = dlg.post_action();
|
||||
upload_job.upload_data.group = dlg.group();
|
||||
upload_job.upload_data.storage = dlg.storage();
|
||||
{
|
||||
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();
|
||||
|
||||
// Show "Is printer clean" dialog for PrusaConnect - Upload and print.
|
||||
if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
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,
|
||||
storage_paths, storage_names,
|
||||
config->get_bool("open_device_tab_post_upload"));
|
||||
} 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"));
|
||||
}
|
||||
|
||||
if (use_3mf) {
|
||||
// Process gcode
|
||||
const int result = send_gcode(plate_idx, nullptr);
|
||||
|
||||
if (result < 0) {
|
||||
wxString msg = _L("Abnormal print file data. Please slice again");
|
||||
show_error(this, msg, false);
|
||||
return;
|
||||
}
|
||||
|
||||
upload_job.upload_data.source_path = p->m_print_job_data._3mf_path;
|
||||
pDlg->init();
|
||||
if (pDlg->ShowModal() != wxID_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
p->export_gcode(fs::path(), false, std::move(upload_job));
|
||||
config->set_bool("open_device_tab_post_upload", pDlg->switch_to_device_tab());
|
||||
// PrintHostUpload upload_data;
|
||||
upload_job.switch_to_device_tab = pDlg->switch_to_device_tab();
|
||||
upload_job.upload_data.upload_path = pDlg->filename();
|
||||
upload_job.upload_data.post_action = pDlg->post_action();
|
||||
upload_job.upload_data.group = pDlg->group();
|
||||
upload_job.upload_data.storage = pDlg->storage();
|
||||
upload_job.upload_data.extended_info = pDlg->extendedInfo();
|
||||
}
|
||||
|
||||
// Show "Is printer clean" dialog for PrusaConnect - Upload and print.
|
||||
if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
}
|
||||
|
||||
if (use_3mf) {
|
||||
// Process gcode
|
||||
const int result = send_gcode(plate_idx, nullptr);
|
||||
|
||||
if (result < 0) {
|
||||
wxString msg = _L("Abnormal print file data. Please slice again");
|
||||
show_error(this, msg, false);
|
||||
return;
|
||||
}
|
||||
|
||||
upload_job.upload_data.source_path = p->m_print_job_data._3mf_path;
|
||||
}
|
||||
|
||||
p->export_gcode(fs::path(), false, std::move(upload_job));
|
||||
}
|
||||
int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn)
|
||||
{
|
||||
|
|
|
@ -47,11 +47,22 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
|||
, post_upload_action(PrintHostPostUploadAction::None)
|
||||
, m_paths(storage_paths)
|
||||
, m_switch_to_device_tab(switch_to_device_tab)
|
||||
, m_path(path)
|
||||
, m_post_actions(post_actions)
|
||||
, m_storage_names(storage_names)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
txt_filename->OSXDisableAllSmartSubstitutions();
|
||||
#endif
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
}
|
||||
void PrintHostSendDialog::init()
|
||||
{
|
||||
const auto& path = m_path;
|
||||
const auto& storage_paths = m_paths;
|
||||
const auto& post_actions = m_post_actions;
|
||||
const auto& storage_names = m_storage_names;
|
||||
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
|
||||
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
|
||||
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
||||
|
@ -614,4 +625,331 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ElegooPrintHostSendDialog::ElegooPrintHostSendDialog(const fs::path& path,
|
||||
PrintHostPostUploadActions post_actions,
|
||||
const wxArrayString& groups,
|
||||
const wxArrayString& storage_paths,
|
||||
const wxArrayString& storage_names,
|
||||
bool switch_to_device_tab)
|
||||
: PrintHostSendDialog(path, post_actions, groups, storage_paths, storage_names, switch_to_device_tab)
|
||||
, m_timeLapse(0)
|
||||
, m_heatedBedLeveling(0)
|
||||
, m_BedType(BedType::btPTE)
|
||||
{}
|
||||
|
||||
void ElegooPrintHostSendDialog::init() {
|
||||
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
|
||||
if (!(model_id == "Elegoo-CC" || model_id == "Elegoo-C")) {
|
||||
PrintHostSendDialog::init();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& path = m_path;
|
||||
const auto& storage_paths = m_paths;
|
||||
const auto& post_actions = m_post_actions;
|
||||
const auto& storage_names = m_storage_names;
|
||||
|
||||
this->SetMinSize(wxSize(500, 300));
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
|
||||
std::string uploadAndPrint = app_config->get("recent", CONFIG_KEY_UPLOADANDPRINT);
|
||||
if (!uploadAndPrint.empty())
|
||||
post_upload_action = static_cast<PrintHostPostUploadAction>(std::stoi(uploadAndPrint));
|
||||
|
||||
std::string timeLapse = app_config->get("recent", CONFIG_KEY_TIMELAPSE);
|
||||
if (!timeLapse.empty())
|
||||
m_timeLapse = std::stoi(timeLapse);
|
||||
std::string heatedBedLeveling = app_config->get("recent", CONFIG_KEY_HEATEDBEDLEVELING);
|
||||
if (!heatedBedLeveling.empty())
|
||||
m_heatedBedLeveling = std::stoi(heatedBedLeveling);
|
||||
std::string bedType = app_config->get("recent", CONFIG_KEY_BEDTYPE);
|
||||
if (!bedType.empty())
|
||||
m_BedType = static_cast<BedType>(std::stoi(bedType));
|
||||
|
||||
auto* label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
|
||||
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
||||
|
||||
wxSizerFlags flags = wxSizerFlags().Border(wxRIGHT, 16).Expand();
|
||||
|
||||
content_sizer->Add(txt_filename, flags);
|
||||
content_sizer->AddSpacer(4);
|
||||
content_sizer->Add(label_dir_hint);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
if (combo_groups != nullptr) {
|
||||
// Repetier specific: Show a selection of file groups.
|
||||
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Group"));
|
||||
content_sizer->Add(label_group);
|
||||
content_sizer->Add(combo_groups, 0, wxBOTTOM, 2 * VERT_SPACING);
|
||||
wxString recent_group = from_u8(app_config->get("recent", CONFIG_KEY_GROUP));
|
||||
if (!recent_group.empty())
|
||||
combo_groups->SetValue(recent_group);
|
||||
}
|
||||
|
||||
if (combo_storage != nullptr) {
|
||||
// PrusaLink specific: User needs to choose a storage
|
||||
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ":");
|
||||
content_sizer->Add(label_group);
|
||||
content_sizer->Add(combo_storage, 0, wxBOTTOM, 2 * VERT_SPACING);
|
||||
combo_storage->SetValue(storage_names.front());
|
||||
wxString recent_storage = from_u8(app_config->get("recent", CONFIG_KEY_STORAGE));
|
||||
if (!recent_storage.empty())
|
||||
combo_storage->SetValue(recent_storage);
|
||||
} else if (storage_names.GetCount() == 1) {
|
||||
// PrusaLink specific: Show which storage has been detected.
|
||||
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ": " + storage_names.front());
|
||||
content_sizer->Add(label_group);
|
||||
m_preselected_storage = m_paths.front();
|
||||
}
|
||||
|
||||
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
|
||||
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
|
||||
recent_path += '/';
|
||||
}
|
||||
const auto recent_path_len = recent_path.Length();
|
||||
recent_path += path.filename().wstring();
|
||||
wxString stem(path.stem().wstring());
|
||||
const auto stem_len = stem.Length();
|
||||
|
||||
txt_filename->SetValue(recent_path);
|
||||
|
||||
{
|
||||
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto checkbox = new ::CheckBox(this, wxID_APPLY);
|
||||
checkbox->SetValue(m_switch_to_device_tab);
|
||||
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
|
||||
m_switch_to_device_tab = e.IsChecked();
|
||||
e.Skip();
|
||||
});
|
||||
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Switch to Device tab after upload."), wxDefaultPosition, wxDefaultSize, 0);
|
||||
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
content_sizer->Add(checkbox_sizer);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
warning_text = new wxStaticText(this, wxID_ANY,
|
||||
_L("The selected bed type does not match the file. Please confirm before starting the print."),
|
||||
wxDefaultPosition, wxDefaultSize, 0);
|
||||
uploadandprint_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto checkbox = new ::CheckBox(this);
|
||||
checkbox->SetValue(post_upload_action == PrintHostPostUploadAction::StartPrint);
|
||||
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
|
||||
if (e.IsChecked()) {
|
||||
post_upload_action = PrintHostPostUploadAction::StartPrint;
|
||||
} else {
|
||||
post_upload_action = PrintHostPostUploadAction::None;
|
||||
}
|
||||
refresh();
|
||||
e.Skip();
|
||||
});
|
||||
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Upload and Print"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
content_sizer->Add(checkbox_sizer);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
|
||||
{
|
||||
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto checkbox = new ::CheckBox(this);
|
||||
checkbox->SetValue(m_timeLapse == 1);
|
||||
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
|
||||
m_timeLapse = e.IsChecked() ? 1 : 0;
|
||||
e.Skip();
|
||||
});
|
||||
checkbox_sizer->AddSpacer(16);
|
||||
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Time-lapse"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
uploadandprint_sizer->Add(checkbox_sizer);
|
||||
uploadandprint_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
|
||||
{
|
||||
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto checkbox = new ::CheckBox(this);
|
||||
checkbox->SetValue(m_heatedBedLeveling == 1);
|
||||
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
|
||||
m_heatedBedLeveling = e.IsChecked() ? 1 : 0;
|
||||
e.Skip();
|
||||
});
|
||||
checkbox_sizer->AddSpacer(16);
|
||||
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Heated Bed Leveling"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
uploadandprint_sizer->Add(checkbox_sizer);
|
||||
uploadandprint_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
|
||||
{
|
||||
auto radioBoxA = new ::RadioBox(this);
|
||||
auto radioBoxB = new ::RadioBox(this);
|
||||
if (m_BedType == BedType::btPC)
|
||||
radioBoxB->SetValue(true);
|
||||
else
|
||||
radioBoxA->SetValue(true);
|
||||
|
||||
radioBoxA->Bind(wxEVT_LEFT_DOWN, [this, radioBoxA, radioBoxB](wxMouseEvent& e) {
|
||||
radioBoxA->SetValue(true);
|
||||
radioBoxB->SetValue(false);
|
||||
m_BedType = BedType::btPTE;
|
||||
refresh();
|
||||
});
|
||||
radioBoxB->Bind(wxEVT_LEFT_DOWN, [this, radioBoxA, radioBoxB](wxMouseEvent& e) {
|
||||
radioBoxA->SetValue(false);
|
||||
radioBoxB->SetValue(true);
|
||||
m_BedType = BedType::btPC;
|
||||
refresh();
|
||||
});
|
||||
|
||||
{
|
||||
auto radio_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
radio_sizer->AddSpacer(16);
|
||||
radio_sizer->Add(radioBoxA, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Textured Build Plate (Side A)"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
radio_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
uploadandprint_sizer->Add(radio_sizer);
|
||||
uploadandprint_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
{
|
||||
auto radio_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
radio_sizer->AddSpacer(16);
|
||||
radio_sizer->Add(radioBoxB, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
|
||||
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Smooth Build Plate (Side B)"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
radio_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
|
||||
checkbox_text->SetFont(::Label::Body_13);
|
||||
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
|
||||
uploadandprint_sizer->Add(radio_sizer);
|
||||
uploadandprint_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
warning_text->SetFont(::Label::Body_13);
|
||||
warning_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#FF1001")));
|
||||
// wrapping the text
|
||||
warning_text->Wrap(350);
|
||||
h_sizer->AddSpacer(16);
|
||||
h_sizer->Add(warning_text);
|
||||
|
||||
uploadandprint_sizer->Add(h_sizer);
|
||||
uploadandprint_sizer->AddSpacer(VERT_SPACING);
|
||||
}
|
||||
|
||||
content_sizer->Add(uploadandprint_sizer);
|
||||
uploadandprint_sizer->Show(post_upload_action == PrintHostPostUploadAction::StartPrint);
|
||||
warning_text->Show(post_upload_action == PrintHostPostUploadAction::StartPrint && appBedType() != m_BedType);
|
||||
|
||||
uploadandprint_sizer->Layout();
|
||||
|
||||
if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos)
|
||||
m_valid_suffix = recent_path.substr(extension_start);
|
||||
// .gcode suffix control
|
||||
auto validate_path = [this](const wxString& path) -> bool {
|
||||
if (!path.Lower().EndsWith(m_valid_suffix.Lower())) {
|
||||
MessageDialog msg_wingow(this,
|
||||
wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"),
|
||||
m_valid_suffix),
|
||||
wxString(SLIC3R_APP_NAME), wxYES | wxNO);
|
||||
if (msg_wingow.ShowModal() == wxID_NO)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto* btn_ok = add_button(wxID_OK, true, _L("Upload"));
|
||||
btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||
if (validate_path(txt_filename->GetValue())) {
|
||||
// post_upload_action = PrintHostPostUploadAction::None;
|
||||
EndDialog(wxID_OK);
|
||||
}
|
||||
});
|
||||
txt_filename->SetFocus();
|
||||
|
||||
add_button(wxID_CANCEL, false, _L("Cancel"));
|
||||
finalize();
|
||||
|
||||
#ifdef __linux__
|
||||
// On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
|
||||
// and as a result the text is invisible with light mode
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/4532
|
||||
// Workaround: Unselect text selection explicitly on kill focus
|
||||
txt_filename->Bind(
|
||||
wxEVT_KILL_FOCUS,
|
||||
[this](wxEvent& e) {
|
||||
e.Skip();
|
||||
txt_filename->SetInsertionPoint(txt_filename->GetLastPosition());
|
||||
},
|
||||
txt_filename->GetId());
|
||||
#endif /* __linux__ */
|
||||
|
||||
Bind(wxEVT_SHOW, [=](const wxShowEvent&) {
|
||||
// Another similar case where the function only works with EVT_SHOW + CallAfter,
|
||||
// this time on Mac.
|
||||
CallAfter([=]() {
|
||||
txt_filename->SetInsertionPoint(0);
|
||||
txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ElegooPrintHostSendDialog::EndModal(int ret)
|
||||
{
|
||||
if (ret == wxID_OK) {
|
||||
|
||||
AppConfig* app_config = wxGetApp().app_config;
|
||||
app_config->set("recent", CONFIG_KEY_UPLOADANDPRINT, std::to_string(static_cast<int>(post_upload_action)));
|
||||
app_config->set("recent", CONFIG_KEY_TIMELAPSE, std::to_string(m_timeLapse));
|
||||
app_config->set("recent", CONFIG_KEY_HEATEDBEDLEVELING, std::to_string(m_heatedBedLeveling));
|
||||
app_config->set("recent", CONFIG_KEY_BEDTYPE, std::to_string(static_cast<int>(m_BedType)));
|
||||
}
|
||||
|
||||
PrintHostSendDialog::EndModal(ret);
|
||||
}
|
||||
|
||||
BedType ElegooPrintHostSendDialog::appBedType() const
|
||||
{
|
||||
std::string str_bed_type = wxGetApp().app_config->get("curr_bed_type");
|
||||
int bed_type_value = atoi(str_bed_type.c_str());
|
||||
return static_cast<BedType>(bed_type_value);
|
||||
}
|
||||
|
||||
void ElegooPrintHostSendDialog::refresh()
|
||||
{
|
||||
if (uploadandprint_sizer) {
|
||||
if (post_upload_action == PrintHostPostUploadAction::StartPrint) {
|
||||
uploadandprint_sizer->Show(true);
|
||||
} else {
|
||||
uploadandprint_sizer->Show(false);
|
||||
}
|
||||
}
|
||||
if (warning_text) {
|
||||
warning_text->Show(post_upload_action == PrintHostPostUploadAction::StartPrint && appBedType() != m_BedType);
|
||||
}
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
}
|
||||
|
||||
}}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "GUI_Utils.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
class wxButton;
|
||||
class wxTextCtrl;
|
||||
class wxChoice;
|
||||
|
@ -27,6 +27,7 @@ class PrintHostSendDialog : public GUI::MsgDialog
|
|||
{
|
||||
public:
|
||||
PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names, bool switch_to_device_tab);
|
||||
virtual ~PrintHostSendDialog() {}
|
||||
boost::filesystem::path filename() const;
|
||||
PrintHostPostUploadAction post_action() const;
|
||||
std::string group() const;
|
||||
|
@ -34,7 +35,10 @@ public:
|
|||
bool switch_to_device_tab() const {return m_switch_to_device_tab;}
|
||||
|
||||
virtual void EndModal(int ret) override;
|
||||
private:
|
||||
virtual void init();
|
||||
virtual std::map<std::string, std::string> extendedInfo() const { return {}; }
|
||||
|
||||
protected:
|
||||
wxTextCtrl *txt_filename;
|
||||
wxComboBox *combo_groups;
|
||||
wxComboBox* combo_storage;
|
||||
|
@ -43,6 +47,10 @@ private:
|
|||
wxString m_preselected_storage;
|
||||
wxArrayString m_paths;
|
||||
bool m_switch_to_device_tab;
|
||||
|
||||
boost::filesystem::path m_path;
|
||||
PrintHostPostUploadActions m_post_actions;
|
||||
wxArrayString m_storage_names;
|
||||
};
|
||||
|
||||
|
||||
|
@ -131,6 +139,47 @@ private:
|
|||
bool load_user_data(int, std::vector<int>&);
|
||||
};
|
||||
|
||||
class ElegooPrintHostSendDialog : public PrintHostSendDialog
|
||||
{
|
||||
public:
|
||||
ElegooPrintHostSendDialog(const boost::filesystem::path& path,
|
||||
PrintHostPostUploadActions post_actions,
|
||||
const wxArrayString& groups,
|
||||
const wxArrayString& storage_paths,
|
||||
const wxArrayString& storage_names,
|
||||
bool switch_to_device_tab);
|
||||
|
||||
virtual void EndModal(int ret) override;
|
||||
int timeLapse() const { return m_timeLapse; }
|
||||
int heatedBedLeveling() const { return m_heatedBedLeveling; }
|
||||
BedType bedType() const { return m_BedType; }
|
||||
|
||||
virtual void init() override;
|
||||
virtual std::map<std::string, std::string> extendedInfo() const
|
||||
{
|
||||
return {{"bedType", std::to_string(static_cast<int>(m_BedType))},
|
||||
{"timeLapse", std::to_string(m_timeLapse)},
|
||||
{"heatedBedLeveling", std::to_string(m_heatedBedLeveling)}};
|
||||
}
|
||||
|
||||
private:
|
||||
BedType appBedType() const;
|
||||
void refresh();
|
||||
|
||||
const char* CONFIG_KEY_UPLOADANDPRINT = "elegoolink_upload_and_print";
|
||||
const char* CONFIG_KEY_TIMELAPSE = "elegoolink_timelapse";
|
||||
const char* CONFIG_KEY_HEATEDBEDLEVELING = "elegoolink_heated_bed_leveling";
|
||||
const char* CONFIG_KEY_BEDTYPE = "elegoolink_bed_type";
|
||||
|
||||
private:
|
||||
wxStaticText* warning_text{nullptr};
|
||||
wxBoxSizer* uploadandprint_sizer{nullptr};
|
||||
|
||||
int m_timeLapse;
|
||||
int m_heatedBedLeveling;
|
||||
BedType m_BedType;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
||||
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
||||
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
||||
|
|
|
@ -3636,13 +3636,29 @@ void TabFilament::toggle_options()
|
|||
{
|
||||
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
|
||||
toggle_option("pressure_advance", pa);
|
||||
// Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected
|
||||
auto support_multi_bed_types = is_BBL_printer || cfg.opt_bool("support_multi_bed_types");
|
||||
toggle_line("supertack_plate_temp_initial_layer", support_multi_bed_types );
|
||||
toggle_line("cool_plate_temp_initial_layer", support_multi_bed_types );
|
||||
toggle_line("textured_cool_plate_temp_initial_layer", support_multi_bed_types);
|
||||
toggle_line("eng_plate_temp_initial_layer", support_multi_bed_types);
|
||||
toggle_line("textured_plate_temp_initial_layer", support_multi_bed_types);
|
||||
|
||||
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
|
||||
DynamicConfig& proj_cfg = m_preset_bundle->project_config;
|
||||
std::string bed_temp_1st_layer_key = "";
|
||||
if (proj_cfg.has("curr_bed_type"))
|
||||
{
|
||||
bed_temp_1st_layer_key = get_bed_temp_1st_layer_key(proj_cfg.opt_enum<BedType>("curr_bed_type"));
|
||||
}
|
||||
|
||||
const std::vector<std::string> bed_temp_keys = {"supertack_plate_temp_initial_layer", "cool_plate_temp_initial_layer",
|
||||
"textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer",
|
||||
"textured_plate_temp_initial_layer", "hot_plate_temp_initial_layer"};
|
||||
|
||||
bool support_multi_bed_types = std::find(bed_temp_keys.begin(), bed_temp_keys.end(), bed_temp_1st_layer_key) ==
|
||||
bed_temp_keys.end() ||
|
||||
is_BBL_printer || cfg.opt_bool("support_multi_bed_types");
|
||||
|
||||
for (const auto& key : bed_temp_keys)
|
||||
{
|
||||
toggle_line(key, support_multi_bed_types || bed_temp_1st_layer_key == key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Orca: adaptive pressure advance and calibration model
|
||||
// If PA is not enabled, disable adaptive pressure advance and hide the model section
|
||||
|
|
863
src/slic3r/Utils/ElegooLink.cpp
Normal file
863
src/slic3r/Utils/ElegooLink.cpp
Normal file
|
@ -0,0 +1,863 @@
|
|||
#include "ElegooLink.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "Bonjour.hpp"
|
||||
#include "slic3r/GUI/BonjourDialog.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
#define MAX_UPLOAD_PACKAGE_LENGTH 1048576 //(1024*1024)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum ElegooLinkCommand {
|
||||
//get status
|
||||
ELEGOO_GET_STATUS = 0,
|
||||
//get properties
|
||||
ELEGOO_GET_PROPERTIES = 1,
|
||||
//start print
|
||||
ELEGOO_START_PRINT = 128,
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SDCP_PRINT_CTRL_ACK_OK = 0, // OK
|
||||
SDCP_PRINT_CTRL_ACK_BUSY = 1 , // 设备忙 device is busy
|
||||
SDCP_PRINT_CTRL_ACK_NOT_FOUND = 2, // 未找到目标文件 file not found
|
||||
SDCP_PRINT_CTRL_ACK_MD5_FAILED = 3, // MD5校验失败 MD5 check failed
|
||||
SDCP_PRINT_CTRL_ACK_FILEIO_FAILED = 4, // 文件读取失败 file I/O error
|
||||
SDCP_PRINT_CTRL_ACK_INVLAID_RESOLUTION = 5, // 文件分辨率不匹配 file resolution is invalid
|
||||
SDCP_PRINT_CTRL_ACK_UNKNOW_FORMAT = 6, // 无法识别的文件格式 file format is invalid
|
||||
SDCP_PRINT_CTRL_ACK_UNKNOW_MODEL = 7, // 文件机型不匹配 file model is invalid
|
||||
} ElegooLinkStartPrintAck;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
std::string get_host_from_url(const std::string& url_in)
|
||||
{
|
||||
std::string url = url_in;
|
||||
// add http:// if there is no scheme
|
||||
size_t double_slash = url.find("//");
|
||||
if (double_slash == std::string::npos)
|
||||
url = "http://" + url;
|
||||
std::string out = url;
|
||||
CURLU* hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
char* host;
|
||||
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
char* port;
|
||||
rc = curl_url_get(hurl, CURLUPART_PORT, &port, 0);
|
||||
if (rc == CURLUE_OK && port != nullptr) {
|
||||
out = std::string(host) + ":" + port;
|
||||
curl_free(port);
|
||||
} else {
|
||||
out = host;
|
||||
curl_free(host);
|
||||
}
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to get host form URL " << url;
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to parse URL " << url;
|
||||
curl_url_cleanup(hurl);
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to allocate curl_url";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string get_host_from_url_no_port(const std::string& url_in)
|
||||
{
|
||||
std::string url = url_in;
|
||||
// add http:// if there is no scheme
|
||||
size_t double_slash = url.find("//");
|
||||
if (double_slash == std::string::npos)
|
||||
url = "http://" + url;
|
||||
std::string out = url;
|
||||
CURLU* hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
char* host;
|
||||
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
out = host;
|
||||
curl_free(host);
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to get host form URL " << url;
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to parse URL " << url;
|
||||
curl_url_cleanup(hurl);
|
||||
}
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to allocate curl_url";
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||
{
|
||||
// put ipv6 into [] brackets
|
||||
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||
sub_addr = "[" + sub_addr + "]";
|
||||
|
||||
#if 0
|
||||
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
|
||||
std::string final_addr = orig_addr;
|
||||
// http
|
||||
size_t double_dash = orig_addr.find("//");
|
||||
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
|
||||
// userinfo
|
||||
size_t at = orig_addr.find("@");
|
||||
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
|
||||
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
|
||||
// or it will be ']' if address is ipv6 )
|
||||
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
|
||||
// if there are more ':' it must be ipv6
|
||||
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
|
||||
size_t ipv6_end = orig_addr.find(']', host_start);
|
||||
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
|
||||
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
|
||||
}
|
||||
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
|
||||
// now host_start and host_end should mark where to put resolved addr
|
||||
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
|
||||
if (host_start >= orig_addr.length()) {
|
||||
return final_addr;
|
||||
}
|
||||
final_addr.replace(host_start, host_end - host_start, sub_addr);
|
||||
return final_addr;
|
||||
#else
|
||||
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||
// If anything fails, return the input unchanged.
|
||||
std::string out = orig_addr;
|
||||
CURLU *hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Extract a string fromt the CURL URL handle.
|
||||
char *url;
|
||||
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
out = url;
|
||||
curl_free(url);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to extract the URL after substitution";
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to parse URL " << orig_addr;
|
||||
curl_url_cleanup(hurl);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to allocate curl_url";
|
||||
return out;
|
||||
#endif
|
||||
}
|
||||
#endif // WIN32
|
||||
std::string escape_string(const std::string& unescaped)
|
||||
{
|
||||
std::string ret_val;
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
|
||||
if (decoded) {
|
||||
ret_val = std::string(decoded);
|
||||
curl_free(decoded);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
std::string escape_path_by_element(const boost::filesystem::path& path)
|
||||
{
|
||||
std::string ret_val = escape_string(path.filename().string());
|
||||
boost::filesystem::path parent(path.parent_path());
|
||||
while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
|
||||
{
|
||||
ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
|
||||
parent = parent.parent_path();
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
} //namespace
|
||||
|
||||
|
||||
ElegooLink::ElegooLink(DynamicPrintConfig *config):
|
||||
OctoPrint(config) {
|
||||
|
||||
}
|
||||
|
||||
const char* ElegooLink::get_name() const { return "ElegooLink"; }
|
||||
|
||||
bool ElegooLink::elegoo_test(wxString& msg) const{
|
||||
|
||||
const char *name = get_name();
|
||||
bool res = true;
|
||||
auto url = make_url("");
|
||||
// Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||
// Check if the response contains "ELEGOO" in any case.
|
||||
// This is a simple check to see if the response is from an ElegooLink server.
|
||||
std::regex re("ELEGOO", std::regex::icase);
|
||||
std::smatch match;
|
||||
if (std::regex_search(body, match, re)) {
|
||||
res = true;
|
||||
} else {
|
||||
msg = format_error(body, "ElegooLink not detected", 0);
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
return res;
|
||||
}
|
||||
bool ElegooLink::test(wxString &curl_msg) const{
|
||||
if(OctoPrint::test(curl_msg)){
|
||||
return true;
|
||||
}
|
||||
curl_msg="";
|
||||
return elegoo_test(curl_msg);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
bool ElegooLink::elegoo_test_with_resolved_ip(wxString& msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
const char* name = get_name();
|
||||
bool res = true;
|
||||
// Msg contains ip string.
|
||||
auto url = substitute_host(make_url(""), GUI::into_u8(msg));
|
||||
msg.Clear();
|
||||
std::string host = get_host_from_url(m_host);
|
||||
auto http = Http::get(url); // std::move(url));
|
||||
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
|
||||
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
|
||||
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
|
||||
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse
|
||||
// proxy is used (issue #9734). Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
|
||||
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
|
||||
http.header("Host", host);
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version at %2% : %3%, HTTP %4%, body: `%5%`") % name % url %
|
||||
error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
// Check if the response contains "ELEGOO" in any case.
|
||||
// This is a simple check to see if the response is from an ElegooLink server.
|
||||
std::regex re("ELEGOO", std::regex::icase);
|
||||
std::smatch match;
|
||||
if (std::regex_search(body, match, re)) {
|
||||
res = true;
|
||||
} else {
|
||||
msg = format_error(body, "ElegooLink not detected", 0);
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
bool ElegooLink::test_with_resolved_ip(wxString& msg) const
|
||||
{
|
||||
// Elegoo supports both otcoprint and Elegoo link
|
||||
if (OctoPrint::test_with_resolved_ip(msg)) {
|
||||
return true;
|
||||
}
|
||||
msg = "";
|
||||
return elegoo_test_with_resolved_ip(msg);
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
|
||||
wxString ElegooLink::get_test_ok_msg() const
|
||||
{
|
||||
return _L("Connection to ElegooLink works correctly.");
|
||||
}
|
||||
|
||||
wxString ElegooLink::get_test_failed_msg(wxString& msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to ElegooLink"), msg);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
bool ElegooLink::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const
|
||||
{
|
||||
wxString test_msg_or_host_ip = "";
|
||||
|
||||
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||
// Test_msg already contains resolved ip and will be cleared on start of test().
|
||||
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||
//Elegoo supports both otcoprint and Elegoo link
|
||||
if(OctoPrint::test_with_resolved_ip(test_msg_or_host_ip)){
|
||||
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
|
||||
}
|
||||
|
||||
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||
if(!elegoo_test_with_resolved_ip(test_msg_or_host_ip)){
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string url = substitute_host(make_url("uploadFile/upload"), resolved_addr.to_string());
|
||||
info_fn(L"resolve", boost::nowide::widen(url));
|
||||
|
||||
bool res = loopUpload(url, upload_data, prorgess_fn, error_fn, info_fn);
|
||||
return res;
|
||||
}
|
||||
#endif //WIN32
|
||||
|
||||
bool ElegooLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||
wxString test_msg_or_host_ip;
|
||||
//Elegoo supports both otcoprint and Elegoo link
|
||||
if(OctoPrint::test(test_msg_or_host_ip)){
|
||||
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
|
||||
}
|
||||
test_msg_or_host_ip="";
|
||||
if(!elegoo_test(test_msg_or_host_ip)){
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
|
||||
#endif // _WIN32
|
||||
{
|
||||
// If https is entered we assume signed ceritificate is being used
|
||||
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||
url = make_url("uploadFile/upload");
|
||||
}
|
||||
#ifdef WIN32
|
||||
else {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||
// If it got the address use it instead of the stored in "host" variable.
|
||||
// This new address returns in "test_msg_or_host_ip" variable.
|
||||
// Solves troubles of uploades failing with name address.
|
||||
// in original address (m_host) replace host for resolved ip
|
||||
info_fn(L"resolve", test_msg_or_host_ip);
|
||||
url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(test_msg_or_host_ip));
|
||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
bool res = loopUpload(url, upload_data, prorgess_fn, error_fn, info_fn);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ElegooLink::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool ElegooLink::uploadPart(Http &http,std::string md5,std::string uuid,std::string path,
|
||||
std::string filename,size_t filesize,size_t offset,size_t length,
|
||||
ProgressFn prorgess_fn,ErrorFn error_fn,InfoFn info_fn)const
|
||||
{
|
||||
const char* name = get_name();
|
||||
bool result = false;
|
||||
http.form_clear();
|
||||
http.form_add("Check", "1")
|
||||
.form_add("S-File-MD5", md5)
|
||||
.form_add("Offset", std::to_string(offset))
|
||||
.form_add("Uuid", uuid)
|
||||
.form_add("TotalSize", std::to_string(filesize))
|
||||
.form_add_file("File", path, filename, offset, length)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
if (status == 200) {
|
||||
try {
|
||||
pt::ptree root;
|
||||
std::istringstream is(body);
|
||||
pt::read_json(is, root);
|
||||
std::string code = root.get<std::string>("code");
|
||||
if (code == "000000") {
|
||||
// info_fn(L"complete", wxString());
|
||||
result = true;
|
||||
} else {
|
||||
// get error messages
|
||||
pt::ptree messages = root.get_child("messages");
|
||||
std::string error_message = "ErrorCode : " + code + "\n";
|
||||
for (pt::ptree::value_type& message : messages) {
|
||||
std::string field = message.second.get<std::string>("field");
|
||||
std::string msg = message.second.get<std::string>("message");
|
||||
error_message += field + ":" + msg + "\n";
|
||||
}
|
||||
error_fn(wxString::FromUTF8(error_message));
|
||||
}
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error parsing response: %2%") % name % body;
|
||||
error_fn(wxString::FromUTF8("Error parsing response"));
|
||||
}
|
||||
} else {
|
||||
error_fn(format_error(body, "upload failed", status));
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status %
|
||||
body;
|
||||
error_fn(format_error(body, error, status));
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
// If upload is finished, do not call progress_fn
|
||||
// on_complete will be called after some time, so we do not need to call it here
|
||||
// Because some devices will call on_complete after the upload progress reaches 100%,
|
||||
// so we need to process it here, based on on_complete
|
||||
if (progress.ultotal == progress.ulnow) {
|
||||
// Upload is finished
|
||||
return;
|
||||
}
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ElegooLink::loopUpload(std::string url, PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
const auto upload_filename = upload_data.upload_path.filename().string();
|
||||
std::string source_path = upload_data.source_path.string();
|
||||
|
||||
// calc file size
|
||||
std::ifstream file(source_path, std::ios::binary | std::ios::ate);
|
||||
std::streamsize size = file.tellg();
|
||||
file.close();
|
||||
const std::string fileSize = std::to_string(size);
|
||||
|
||||
// generate uuid
|
||||
boost::uuids::random_generator generator;
|
||||
boost::uuids::uuid uuid = generator();
|
||||
std::string uuid_string = to_string(uuid);
|
||||
|
||||
std::string md5;
|
||||
bbl_calc_md5(source_path, md5);
|
||||
|
||||
auto http = Http::post(url);
|
||||
#ifdef WIN32
|
||||
// "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted
|
||||
// it into "url" variable. And when creating Http object above, libcurl automatically includes "Host" header from address it got.
|
||||
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. Not changing the host would work
|
||||
// on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). Also
|
||||
// when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
|
||||
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
|
||||
std::string host = get_host_from_url(m_host);
|
||||
http.header("Host", host);
|
||||
http.header("Accept", "application/json, text/plain, */*");
|
||||
#endif // _WIN32
|
||||
set_auth(http);
|
||||
|
||||
bool res = false;
|
||||
const int packageCount = (size + MAX_UPLOAD_PACKAGE_LENGTH - 1) / MAX_UPLOAD_PACKAGE_LENGTH;
|
||||
|
||||
for (size_t i = 0; i < packageCount; i++) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2%/%3%") % name % (i + 1) % packageCount;
|
||||
const size_t offset = MAX_UPLOAD_PACKAGE_LENGTH * i;
|
||||
size_t length = MAX_UPLOAD_PACKAGE_LENGTH;
|
||||
// if it is the last package, the length is the remainder of the file size divided by MAX_UPLOAD_PACKAGE_LENGTH
|
||||
if ((i == packageCount - 1) && (size % MAX_UPLOAD_PACKAGE_LENGTH > 0)) {
|
||||
length = size % MAX_UPLOAD_PACKAGE_LENGTH;
|
||||
}
|
||||
res = uploadPart(
|
||||
http, md5, uuid_string, source_path, upload_filename, size, offset, length,
|
||||
[size, i, progress_fn](Http::Progress progress, bool& cancel) {
|
||||
Http::Progress p(0, 0, size, i * MAX_UPLOAD_PACKAGE_LENGTH + progress.ulnow, progress.buffer);
|
||||
progress_fn(p, cancel);
|
||||
},
|
||||
error_fn, info_fn);
|
||||
if (!res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
// connect to websocket, since the upload is successful, the file will be printed
|
||||
std::string wsUrl = get_host_from_url_no_port(m_host);
|
||||
WebSocketClient client;
|
||||
try {
|
||||
client.connect(wsUrl, "3030", "/websocket");
|
||||
} catch (std::exception& e) {
|
||||
const auto errorString = std::string(e.what());
|
||||
if (errorString.find("The WebSocket handshake was declined by the remote peer") != std::string::npos) {
|
||||
// error_fn(
|
||||
// _L("The number of printer connections has exceeded the limit. Please disconnect other connections, restart the "
|
||||
// "printer and slicer, and then try again."));
|
||||
error_fn(_L("The file has been transferred, but some unknown errors occurred. Please check the device page for the file and try to start printing again."));
|
||||
} else {
|
||||
error_fn(std::string("\n") + wxString::FromUTF8(e.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::string timeLapse = "0";
|
||||
std::string heatedBedLeveling = "0";
|
||||
std::string bedType = "0";
|
||||
timeLapse = upload_data.extended_info["timeLapse"];
|
||||
heatedBedLeveling = upload_data.extended_info["heatedBedLeveling"];
|
||||
bedType = upload_data.extended_info["bedType"];
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (checkResult(client, error_fn)) {
|
||||
// send print command
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
res = print(client, timeLapse, heatedBedLeveling, bedType, upload_filename, error_fn);
|
||||
}else{
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ElegooLink::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
#ifndef WIN32
|
||||
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||
#else
|
||||
std::string host = get_host_from_url(m_host);
|
||||
|
||||
// decide what to do based on m_host - resolve hostname or upload to ip
|
||||
std::vector<boost::asio::ip::address> resolved_addr;
|
||||
boost::system::error_code ec;
|
||||
boost::asio::ip::address host_ip = boost::asio::ip::make_address(host, ec);
|
||||
if (!ec) {
|
||||
resolved_addr.push_back(host_ip);
|
||||
} else if ( GUI::get_app_config()->get_bool("allow_ip_resolve") && boost::algorithm::ends_with(host, ".local")){
|
||||
Bonjour("octoprint")
|
||||
.set_hostname(host)
|
||||
.set_retries(5) // number of rounds of queries send
|
||||
.set_timeout(1) // after each timeout, if there is any answer, the resolving will stop
|
||||
.on_resolve([&ra = resolved_addr](const std::vector<BonjourReply>& replies) {
|
||||
for (const auto & rpl : replies) {
|
||||
boost::asio::ip::address ip(rpl.ip);
|
||||
ra.emplace_back(ip);
|
||||
BOOST_LOG_TRIVIAL(info) << "Resolved IP address: " << rpl.ip;
|
||||
}
|
||||
})
|
||||
.resolve_sync();
|
||||
}
|
||||
if (resolved_addr.empty()) {
|
||||
// no resolved addresses - try system resolving
|
||||
BOOST_LOG_TRIVIAL(error) << "ElegooLink failed to resolve hostname " << m_host << " into the IP address. Starting upload with system resolving.";
|
||||
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||
} else if (resolved_addr.size() == 1) {
|
||||
// one address resolved - upload there
|
||||
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr.front());
|
||||
} else if (resolved_addr.size() == 2 && resolved_addr[0].is_v4() != resolved_addr[1].is_v4()) {
|
||||
// there are just 2 addresses and 1 is ip_v4 and other is ip_v6
|
||||
// try sending to both. (Then if both fail, show both error msg after second try)
|
||||
wxString error_message;
|
||||
if (!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
|
||||
, [&msg = error_message, resolved_addr](wxString error) { msg = GUI::format_wxstr("%1%: %2%", resolved_addr.front(), error); }
|
||||
, info_fn, resolved_addr.front())
|
||||
&&
|
||||
!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
|
||||
, [&msg = error_message, resolved_addr](wxString error) { msg += GUI::format_wxstr("\n%1%: %2%", resolved_addr.back(), error); }
|
||||
, info_fn, resolved_addr.back())
|
||||
) {
|
||||
|
||||
error_fn(error_message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// There are multiple addresses - user needs to choose which to use.
|
||||
size_t selected_index = resolved_addr.size();
|
||||
IPListDialog dialog(nullptr, boost::nowide::widen(m_host), resolved_addr, selected_index);
|
||||
if (dialog.ShowModal() == wxID_OK && selected_index < resolved_addr.size()) {
|
||||
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr[selected_index]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
bool ElegooLink::print(WebSocketClient& client,
|
||||
std::string timeLapse,
|
||||
std::string heatedBedLeveling,
|
||||
std::string bedType,
|
||||
const std::string filename,
|
||||
ErrorFn error_fn) const
|
||||
{
|
||||
|
||||
// convert bedType to 0 or 1, 0 is PTE, 1 is PC
|
||||
if (bedType == std::to_string((int)BedType::btPC)){
|
||||
bedType = "1";
|
||||
}else{
|
||||
bedType = "0";
|
||||
}
|
||||
bool res = false;
|
||||
// create a random UUID generator
|
||||
boost::uuids::random_generator generator;
|
||||
// generate a UUID
|
||||
boost::uuids::uuid uuid = generator();
|
||||
std::string uuid_string = to_string(uuid);
|
||||
try {
|
||||
std::string requestID = uuid_string;
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
||||
std::string timestamp = std::to_string(milliseconds);
|
||||
std::string jsonString = R"({
|
||||
"Id":"",
|
||||
"Data":{
|
||||
"Cmd":)"+std::to_string(ElegooLinkCommand::ELEGOO_START_PRINT)+R"(,
|
||||
"Data":{
|
||||
"Filename":"/local/)" + filename + R"(",
|
||||
"StartLayer":0,
|
||||
"Calibration_switch":)" + heatedBedLeveling + R"(,
|
||||
"PrintPlatformType":)" + bedType + R"(,
|
||||
"Tlp_Switch":)" + timeLapse + R"(
|
||||
},
|
||||
"RequestID":")"+ uuid_string + R"(",
|
||||
"MainboardID":"",
|
||||
"TimeStamp":)" + timestamp + R"(,
|
||||
"From":1
|
||||
}
|
||||
})";
|
||||
std::cout << "send: " << jsonString << std::endl;
|
||||
BOOST_LOG_TRIVIAL(info) << "start print, param: " << jsonString;
|
||||
client.send(jsonString);
|
||||
// wait 30s
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
do{
|
||||
std::string response = client.receive();
|
||||
std::cout << "Received: " << response << std::endl;
|
||||
BOOST_LOG_TRIVIAL(info) << "Received: " << response;
|
||||
|
||||
//sample response
|
||||
// {"Id":"979d4C788A4a78bC777A870F1A02867A","Data":{"Cmd":128,"Data":{"Ack":1},"RequestID":"5223de52cc7642ae8d7924f9dd46f6ad","MainboardID":"1c7319d30105041800009c0000000000","TimeStamp":1735032553},"Topic":"sdcp/response/1c7319d30105041800009c0000000000"}
|
||||
pt::ptree root;
|
||||
std::istringstream is(response);
|
||||
pt::read_json(is, root);
|
||||
|
||||
auto data = root.get_child_optional("Data");
|
||||
if(!data){
|
||||
BOOST_LOG_TRIVIAL(info) << "wait for start print response";
|
||||
continue;
|
||||
}
|
||||
auto cmd = data->get_optional<int>("Cmd");
|
||||
if(!cmd){
|
||||
BOOST_LOG_TRIVIAL(info) << "wait for start print response";
|
||||
continue;
|
||||
}
|
||||
if(*cmd == ElegooLinkCommand::ELEGOO_START_PRINT){
|
||||
auto _ack = data->get_optional<int>("Data.Ack");
|
||||
if(!_ack){
|
||||
BOOST_LOG_TRIVIAL(error) << "start print failed, ack not found";
|
||||
error_fn(_L("Error code not found"));
|
||||
break;
|
||||
}
|
||||
auto ack = static_cast<ElegooLinkStartPrintAck>(*_ack);
|
||||
if(ack == ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_OK){
|
||||
res = true;
|
||||
}else{
|
||||
res = false;
|
||||
BOOST_LOG_TRIVIAL(error) << "start print failed: ack: " << ack;
|
||||
wxString error_message = "";
|
||||
switch(ack){
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_BUSY:
|
||||
{
|
||||
error_message =_L("The printer is busy, Please check the device page for the file and try to start printing again.");
|
||||
break;
|
||||
}
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_NOT_FOUND:
|
||||
{
|
||||
error_message =_(L("The file is lost, please check and try again."));
|
||||
break;
|
||||
}
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_MD5_FAILED:
|
||||
{
|
||||
error_message =_(L("The file is corrupted, please check and try again."));
|
||||
break;
|
||||
}
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_FILEIO_FAILED:
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_INVLAID_RESOLUTION:
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_UNKNOW_FORMAT:
|
||||
{
|
||||
error_message =_(L("Transmission abnormality, please check and try again."));
|
||||
break;
|
||||
}
|
||||
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_UNKNOW_MODEL:
|
||||
{
|
||||
error_message =_(L("The file does not match the printer, please check and try again."));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
error_message =_L("Unknown error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error_message += " " + wxString::Format(_L("Error code: %d"),ack);
|
||||
error_fn(error_message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30));
|
||||
if (std::chrono::steady_clock::now() - start_time >= std::chrono::seconds(30)) {
|
||||
res = false;
|
||||
error_fn(_L("Start print timeout"));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
BOOST_LOG_TRIVIAL(error) << "start print error: " << e.what();
|
||||
error_fn(_L("Start print failed") +"\n" +GUI::from_u8(e.what()));
|
||||
res=false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ElegooLink::checkResult(WebSocketClient& client, ErrorFn error_fn) const
|
||||
{
|
||||
bool res = true;
|
||||
// create a random UUID generator
|
||||
boost::uuids::random_generator generator;
|
||||
// generate a UUID
|
||||
boost::uuids::uuid uuid = generator();
|
||||
std::string uuid_string = to_string(uuid);
|
||||
try {
|
||||
std::string requestID = uuid_string;
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
||||
std::string timestamp = std::to_string(milliseconds);
|
||||
std::string jsonString = R"({
|
||||
"Id":"",
|
||||
"Data":{
|
||||
"Cmd":)" +
|
||||
std::to_string(ElegooLinkCommand::ELEGOO_GET_STATUS) + R"(,
|
||||
"Data":{},
|
||||
"RequestID":")" +
|
||||
uuid_string + R"(",
|
||||
"MainboardID":"",
|
||||
"TimeStamp":)" +
|
||||
timestamp + R"(,
|
||||
"From":1
|
||||
}
|
||||
})";
|
||||
std::cout << "send: " << jsonString << std::endl;
|
||||
BOOST_LOG_TRIVIAL(info) << "start print, param: " << jsonString;
|
||||
bool needWrite = true;
|
||||
// wait 60s
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
do {
|
||||
if (needWrite) {
|
||||
client.send(jsonString);
|
||||
needWrite = false;
|
||||
}
|
||||
std::string response = client.receive();
|
||||
std::cout << "Received: " << response << std::endl;
|
||||
BOOST_LOG_TRIVIAL(info) << "Received: " << response;
|
||||
pt::ptree root;
|
||||
std::istringstream is(response);
|
||||
pt::read_json(is, root);
|
||||
auto status = root.get_child_optional("Status");
|
||||
if (status) {
|
||||
auto currentStatus = status->get_child_optional("CurrentStatus");
|
||||
if (currentStatus) {
|
||||
std::vector<int> status;
|
||||
for (auto& item : *currentStatus) {
|
||||
status.push_back(item.second.get_value<int>());
|
||||
}
|
||||
if (std::find(status.begin(), status.end(), 8) != status.end()) {
|
||||
// 8 is check file status, need to wait
|
||||
needWrite = true;
|
||||
// sleep 1s
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(60));
|
||||
if (std::chrono::steady_clock::now() - start_time >= std::chrono::seconds(60)) {
|
||||
res = false;
|
||||
error_fn(_L("Start print timeout"));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
BOOST_LOG_TRIVIAL(error) << "start print error: " << e.what();
|
||||
error_fn(_L("Start print failed") + "\n" + GUI::from_u8(e.what()));
|
||||
res = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
72
src/slic3r/Utils/ElegooLink.hpp
Normal file
72
src/slic3r/Utils/ElegooLink.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#ifndef slic3r_ElegooLink_hpp_
|
||||
#define slic3r_ElegooLink_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "OctoPrint.hpp"
|
||||
#include "WebSocketClient.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class ElegooLink : public OctoPrint
|
||||
{
|
||||
public:
|
||||
ElegooLink(DynamicPrintConfig *config);
|
||||
~ElegooLink() override = default;
|
||||
const char* get_name() const override;
|
||||
virtual bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
protected:
|
||||
#ifdef WIN32
|
||||
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
|
||||
#endif
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||
|
||||
#ifdef WIN32
|
||||
virtual bool test_with_resolved_ip(wxString& curl_msg) const override;
|
||||
bool elegoo_test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool elegoo_test(wxString& curl_msg) const;
|
||||
bool print(WebSocketClient& client,
|
||||
std::string timeLapse,
|
||||
std::string heatedBedLeveling,
|
||||
std::string bedType,
|
||||
const std::string filename, ErrorFn error_fn) const;
|
||||
bool checkResult(WebSocketClient& client,
|
||||
ErrorFn error_fn) const;
|
||||
|
||||
bool loopUpload(std::string url, PrintHostUpload upload_data,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const;
|
||||
|
||||
bool uploadPart(Http &http,
|
||||
std::string md5,
|
||||
std::string uuid,
|
||||
std::string path,
|
||||
std::string filename,
|
||||
size_t filesize,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -192,6 +192,11 @@ Http::priv::priv(const std::string &url)
|
|||
::curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
|
||||
// https://everything.curl.dev/http/post/expect100.html
|
||||
// remove the Expect: header, it will add a second delay to each request,
|
||||
// if the file is uploaded in packets, it will cause the upload time to be longer
|
||||
headerlist = curl_slist_append(headerlist, "Expect:");
|
||||
}
|
||||
|
||||
Http::priv::~priv()
|
||||
|
@ -599,6 +604,21 @@ Http& Http::ca_file(const std::string &name)
|
|||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_clear() {
|
||||
if (p) {
|
||||
if (p->form) {
|
||||
::curl_formfree(p->form);
|
||||
p->form = nullptr;
|
||||
p->form_end = nullptr;
|
||||
}
|
||||
for (auto &f : p->form_files) {
|
||||
f.ifs.close();
|
||||
}
|
||||
p->form_files.clear();
|
||||
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add(const std::string &name, const std::string &contents)
|
||||
{
|
||||
|
|
|
@ -119,6 +119,7 @@ public:
|
|||
// See also ca_file_supported().
|
||||
Http& ca_file(const std::string &filename);
|
||||
|
||||
Http& form_clear();
|
||||
// Add a HTTP multipart form field
|
||||
Http& form_add(const std::string &name, const std::string &contents);
|
||||
// Add a HTTP multipart form file data contents, `name` is the name of the part
|
||||
|
|
|
@ -49,9 +49,8 @@ protected:
|
|||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
|
||||
private:
|
||||
#ifdef WIN32
|
||||
bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||
virtual bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "Obico.hpp"
|
||||
#include "Flashforge.hpp"
|
||||
#include "SimplyPrint.hpp"
|
||||
#include "ElegooLink.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::optional;
|
||||
|
@ -65,6 +66,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
|||
case htObico: return new Obico(config);
|
||||
case htFlashforge: return new Flashforge(config);
|
||||
case htSimplyPrint: return new SimplyPrint(config);
|
||||
case htElegooLink: return new ElegooLink(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
@ -78,7 +80,16 @@ wxString PrintHost::format_error(const std::string &body, const std::string &err
|
|||
auto wxbody = wxString::FromUTF8(body.data());
|
||||
return wxString::Format("HTTP %u: %s", status, wxbody);
|
||||
} else {
|
||||
return wxString::FromUTF8(error.data());
|
||||
if (error.find("curl:Timeout was reached") != std::string::npos) {
|
||||
return _L("Connection timed out. Please check if the printer and computer network are functioning properly, and confirm that they are on the same network.");
|
||||
}else if(error.find("curl:Couldn't resolve host name")!= std::string::npos){
|
||||
return _L("The Hostname/IP/URL could not be parsed, please check it and try again.");
|
||||
} else if (error.find("Connection was reset") != std::string::npos){
|
||||
return _L("File/data transfer interrupted. Please check the printer and network, then try it again.");
|
||||
}
|
||||
else {
|
||||
return wxString::FromUTF8(error.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include <libslic3r/enum_bitmask.hpp>
|
||||
#include "Http.hpp"
|
||||
|
||||
#include <map>
|
||||
class wxArrayString;
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -37,6 +37,9 @@ struct PrintHostUpload
|
|||
std::string storage;
|
||||
|
||||
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
|
||||
|
||||
// Some extended parameters for different upload methods.
|
||||
std::map<std::string, std::string> extended_info;
|
||||
};
|
||||
|
||||
class PrintHost
|
||||
|
|
101
src/slic3r/Utils/WebSocketClient.hpp
Normal file
101
src/slic3r/Utils/WebSocketClient.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#ifndef _WEB_SOCKET_CLIENT_HPP_
|
||||
#define _WEB_SOCKET_CLIENT_HPP_
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
|
||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
|
||||
class WebSocketClient {
|
||||
public:
|
||||
//服务器是ws://echo.websocket.org:80/websocket
|
||||
WebSocketClient():
|
||||
resolver_(ioc_), ws_(ioc_),is_connect(false) {
|
||||
|
||||
}
|
||||
|
||||
~WebSocketClient() {
|
||||
if(!is_connect){
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Close the WebSocket connection
|
||||
ws_.close(websocket::close_code::normal);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
void connect(const std::string& host, const std::string& port, const std::string& path="/"){
|
||||
if(is_connect){
|
||||
return;
|
||||
}
|
||||
// Look up the domain name
|
||||
auto const results = resolver_.resolve(host, port);
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
auto ep = net::connect(ws_.next_layer(), results);
|
||||
std::string _host = host;
|
||||
//if _host last char is '/', remove it
|
||||
if(_host.size()>0&&_host[host.size()-1] == '/'){
|
||||
_host[host.size()-1] = '\0';
|
||||
}
|
||||
|
||||
// _host += ':' + std::to_string(ep.port());
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
ws_.set_option(websocket::stream_base::decorator(
|
||||
[](websocket::request_type& req)
|
||||
{
|
||||
req.set(http::field::user_agent,"ElegooSlicer");
|
||||
}));
|
||||
// Perform the WebSocket handshake
|
||||
ws_.handshake(_host, path);
|
||||
is_connect = true;
|
||||
}
|
||||
|
||||
void send(const std::string& message){
|
||||
// Send a message
|
||||
ws_.write(net::buffer(message));
|
||||
}
|
||||
|
||||
std::string receive(int timeout = 0){
|
||||
// This buffer will hold the incoming message
|
||||
beast::flat_buffer buffer;
|
||||
|
||||
// Read a message into our buffer
|
||||
ws_.read(buffer);
|
||||
|
||||
// Return the message as a string
|
||||
return beast::buffers_to_string(buffer.data());
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
net::io_context ioc_;
|
||||
tcp::resolver resolver_;
|
||||
websocket::stream<tcp::socket> ws_;
|
||||
bool is_connect;
|
||||
};
|
||||
|
||||
// int main() {
|
||||
// try {
|
||||
// WebSocketClient client("echo.websocket.org", "80");
|
||||
|
||||
// client.send("Hello, world!");
|
||||
// std::string response = client.receive();
|
||||
|
||||
// std::cout << "Received: " << response << std::endl;
|
||||
// } catch (const std::exception& e) {
|
||||
// std::cerr << "Error: " << e.what() << std::endl;
|
||||
// return EXIT_FAILURE;
|
||||
// }
|
||||
|
||||
// return EXIT_SUCCESS;
|
||||
// }
|
||||
#endif // _WEB_SOCKET_CLIENT_HPP
|
Loading…
Add table
Add a link
Reference in a new issue