OrcaSlicer/src/slic3r/GUI/StepMeshDialog.cpp
yw4z 0bee82cee5
Hyperlink class (#9947)
### FIXES
• 3mf file version check dialog opens bambu releases page instead Orca

### CODE COMPARISON

<img width="112" height="36" alt="Screenshot-20251128125737" src="https://github.com/user-attachments/assets/73718a18-8159-43d5-bb80-0eb90d59a8f6" />

**wxHyperlinkCtrl**
• System decides what colors to use. so blue color is visible even with colors set
• No need to use SetCursor()
```
auto wiki_url = "https://github.com/OrcaSlicer/OrcaSlicer/wiki/Built-in-placeholders-variables";
wxHyperlinkCtrl* wiki = new wxHyperlinkCtrl(this, wxID_ANY, _L("Wiki Guide"), wiki_url);
wiki->SetToolTip(wiki_url); // required to showing navigation point to user
wiki->SetFont(Label::Body_14); // not works properly
wiki->SetVisitedColour(wxColour("#009687")); // not works properly
wiki->SetHoverColour(  wxColour("#26A69A")); // not works properly
wiki->SetNormalColour( wxColour("#009687")); // not works properly
```

<img width="132" height="39" alt="Screenshot-20251128125847" src="https://github.com/user-attachments/assets/f6818dc0-5078-498a-bf09-1fd36e81ebe5" />

**wxStaticText**
• Works reliably on colors and fonts
• All event has to defined manually
```
wxStaticText* wiki = new wxStaticText(this, wxID_ANY, _L("Wiki Guide"));
auto wiki_url = "https://github.com/OrcaSlicer/OrcaSlicer/wiki/Built-in-placeholders-variables";
wiki->SetToolTip(wiki_url); // required to showing navigation point to user
wiki->SetForegroundColour(wxColour("#009687"));
wiki->SetCursor(wxCURSOR_HAND);
wxFont font = Label::Body_14;
font.SetUnderlined(true);
wiki->SetFont(font);
wiki->Bind(wxEVT_LEFT_DOWN   ,[this, wiki_url](wxMouseEvent e) {wxLaunchDefaultBrowser(wiki_url);});
wiki->Bind(wxEVT_ENTER_WINDOW,[this, wiki    ](wxMouseEvent e) {SetForegroundColour(wxColour("#26A69A"));});
wiki->Bind(wxEVT_LEAVE_WINDOW,[this, wiki    ](wxMouseEvent e) {SetForegroundColour(wxColour("#009687"));});
```

<img width="132" height="39" alt="Screenshot-20251128125847" src="https://github.com/user-attachments/assets/f6818dc0-5078-498a-bf09-1fd36e81ebe5" />

**HyperLink**
• Fully automated and single line solution
• Colors can be controllable from one place
• Works reliably on colors and fonts
• Reduces duplicate code
```
HyperLink* wiki = new HyperLink(this, _L("Wiki Guide"), "https://github.com/OrcaSlicer/OrcaSlicer/wiki/Built-in-placeholders-variables");
wiki->SetFont(Label::Body_14) // OPTIONAL default is Label::Body_14;
```


### CHANGES
• Unifies all hyperlinks with same style and makes them controllable from one place
• Replaces all wxHyperlink with simple custom class. Problem with wxHyperlink it mostly rendered as blue even color set
• Reduces duplicate code
• Adds wiki links for calibration dialogs
• Probably will add "Wiki Guide" to more dialogs overtime

<img width="349" height="238" alt="Screenshot-20251127212007" src="https://github.com/user-attachments/assets/69da2732-ea35-44de-8ebc-97a01f86328f" />

<img width="355" height="459" alt="Screenshot-20251127212021" src="https://github.com/user-attachments/assets/c0df40f8-c15d-47fa-b31a-cf8d8b337472" />

<img width="442" height="382" alt="Screenshot-20251127212046" src="https://github.com/user-attachments/assets/5d94242b-6364-4b0a-8b2f-a1f482199bd1" />

<img width="225" height="241" alt="Screenshot-20250824171339" src="https://github.com/user-attachments/assets/39ca6af3-6f8a-42ee-bf1d-c13d0f54bb63" />

<img width="442" height="639" alt="Screenshot-20251127212403" src="https://github.com/user-attachments/assets/c1c580f8-3e1b-42f0-aa8e-bac41c2ff76b" />

<img width="476" height="286" alt="Screenshot-20251127212515" src="https://github.com/user-attachments/assets/28b130ce-c7c0-4ada-9842-ff7154c00c21" />

<img width="1460" height="245" alt="Screenshot-20251127212541" src="https://github.com/user-attachments/assets/3fca2649-9cd3-4aea-9153-b2f508fdfefe" />

<img width="401" height="291" alt="Screenshot-20251127213243" src="https://github.com/user-attachments/assets/82b4ec1f-6074-4018-9efa-a1b6b819ae28" />
2026-01-03 23:06:57 +08:00

361 lines
No EOL
16 KiB
C++

#include "StepMeshDialog.hpp"
#include <thread>
#include <wx/event.h>
#include <wx/sizer.h>
#include <wx/slider.h>
#include <wx/dcmemory.h>
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "MainFrame.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/DialogButtons.hpp"
#include <chrono>
using namespace Slic3r;
using namespace Slic3r::GUI;
static int _scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; }
static int _ITEM_WIDTH() { return _scale(30); }
#define MIN_DIALOG_WIDTH FromDIP(400)
#define SLIDER_WIDTH FromDIP(200)
#define SLIDER_HEIGHT FromDIP(25)
#define TEXT_CTRL_WIDTH FromDIP(70)
#define BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24))
#define BUTTON_BORDER FromDIP(int(400 - 58 * 2) / 8)
#define SLIDER_SCALE(val) ((val) / 0.001)
#define SLIDER_UNSCALE(val) ((val) * 0.001)
#define SLIDER_SCALE_10(val) ((val) / 0.01)
#define SLIDER_UNSCALE_10(val) ((val) * 0.01)
#define LEFT_RIGHT_PADING FromDIP(20)
#define FONT_COLOR wxColour("#6B6B6B")
wxDEFINE_EVENT(wxEVT_THREAD_DONE, wxCommandEvent);
class CenteredStaticText : public wxStaticText
{
public:
CenteredStaticText(wxWindow* parent, wxWindowID id, const wxString& label, const wxPoint& position, const wxSize& size = wxDefaultSize, long style = 0)
: wxStaticText(parent, id, label, position, size, style) {
CenterOnPosition(position);
}
void CenterOnPosition(const wxPoint& position) {
int textWidth, textHeight;
GetTextExtent(GetLabel(), &textWidth, &textHeight);
int x = position.x - textWidth / 2;
int y = position.y - textHeight / 2;
SetPosition(wxPoint(x, y));
}
};
void StepMeshDialog::on_dpi_changed(const wxRect& suggested_rect) {
};
bool StepMeshDialog:: validate_number_range(const wxString& value, double min, double max) {
double num = 0.0;
if (value.IsEmpty()) {
return false;
}
try {
if (!value.ToDouble(&num)) {
return false;
}
} catch (...) {
return false;
}
return (num >= min && num <= max);
}
StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double linear_init, double angle_init)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY,
_(L("Step file import parameters")),
wxDefaultPosition,
wxDefaultSize,
wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/), m_file(file)
{
m_linear_last = wxString::Format("%.3f", linear_init);
m_angle_last = wxString::Format("%.2f", angle_init);
Bind(wxEVT_THREAD_DONE, &StepMeshDialog::on_task_done, this);
SetBackgroundColour(*wxWHITE);
wxBoxSizer* bSizer = new wxBoxSizer(wxVERTICAL);
bSizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1));
auto image_bitmap = create_scaled_bitmap("step_mesh_info", this, FromDIP(120));
// wxPanel* overlay_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, image_bitmap.GetSize(), wxTAB_TRAVERSAL);
// overlay_panel->SetBackgroundStyle(wxBG_STYLE_PAINT);
// wxStaticBitmap *image = new wxStaticBitmap(overlay_panel, wxID_ANY, image_bitmap, wxDefaultPosition, overlay_panel->GetSize(), 0);
// CenteredStaticText* text_1 = new CenteredStaticText (overlay_panel, wxID_ANY, _L("Smooth"),
// wxPoint(overlay_panel->GetSize().GetWidth() / 6,
// overlay_panel->GetSize().GetHeight() / 2));
// CenteredStaticText* text_2 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Rough"),
// wxPoint(overlay_panel->GetSize().GetWidth() * 5 / 6,
// overlay_panel->GetSize().GetHeight() / 2));
// CenteredStaticText* text_3 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Reduce Linear"),
// wxPoint(overlay_panel->GetSize().GetWidth() / 2,
// overlay_panel->GetSize().GetHeight() * 1.3 / 3));
// CenteredStaticText* text_4 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Reduce Angle"),
// wxPoint(overlay_panel->GetSize().GetWidth() / 2,
// overlay_panel->GetSize().GetHeight() * 2 / 3));
// CenteredStaticText* text_5 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("More faces"),
// wxPoint(overlay_panel->GetSize().GetWidth() / 6,
// overlay_panel->GetSize().GetHeight() * 2.8 / 3));
// CenteredStaticText* text_6 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Fewer faces"),
// wxPoint(overlay_panel->GetSize().GetWidth() * 5 / 6,
// overlay_panel->GetSize().GetHeight() * 2.8 / 3));
// bSizer->Add(overlay_panel, 0, wxALIGN_CENTER | wxALL, 10);
wxBoxSizer* tips_sizer = new wxBoxSizer(wxVERTICAL);
wxStaticText* info = new wxStaticText(this, wxID_ANY, _L("Smaller linear and angular deflections result in higher-quality transformations but increase the processing time."));
info->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
// ORCA standardized HyperLink
HyperLink *tips = new HyperLink(this, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/stl-transformation");
tips->SetFont(::Label::Body_12);
info->Wrap(FromDIP(400));
tips_sizer->Add(info, 0, wxALIGN_LEFT);
tips_sizer->Add(tips, 0, wxALIGN_LEFT);
bSizer->Add(tips_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
wxBoxSizer* linear_sizer = new wxBoxSizer(wxHORIZONTAL);
//linear_sizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1));
wxStaticText* linear_title = new wxStaticText(this,
wxID_ANY, _L("Linear Deflection") + ": ");
linear_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
linear_sizer->Add(linear_title, 0, wxALIGN_LEFT);
linear_sizer->AddStretchSpacer(1);
wxSlider* linear_slider = new wxSlider(this, wxID_ANY,
SLIDER_SCALE(get_linear_defletion()),
1, 100, wxDefaultPosition,
wxSize(SLIDER_WIDTH, SLIDER_HEIGHT),
wxSL_HORIZONTAL);
linear_sizer->Add(linear_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
auto linear_input = new ::TextInput(this, m_linear_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER);
linear_input->GetTextCtrl()->SetFont(Label::Body_12);
linear_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
linear_sizer->Add(linear_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
linear_input->Bind(wxEVT_KILL_FOCUS, ([this, linear_input](wxFocusEvent& e) {
wxString value = linear_input->GetTextCtrl()->GetValue();
if (validate_number_range(value, 0.001, 0.1)) {
m_linear_last = value;
update_mesh_number_text();
} else {
MessageDialog msg_dlg(nullptr, _L("Please input a valid value (0.001 < linear deflection < 0.1)"), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
linear_input->GetTextCtrl()->SetValue(m_linear_last);
}
e.Skip();
}));
// textctrl bind slider
linear_input->Bind(wxEVT_TEXT, ([this, linear_slider, linear_input](wxCommandEvent& e) {
double slider_value_long;
int slider_value;
wxString value = linear_input->GetTextCtrl()->GetValue();
if (value.ToDouble(&slider_value_long)) {
slider_value = SLIDER_SCALE(slider_value_long);
if (slider_value >= linear_slider->GetMin() && slider_value <= linear_slider->GetMax()) {
linear_slider->SetValue(slider_value);
}
}
}));
linear_slider->Bind(wxEVT_SLIDER, ([this, linear_slider, linear_input](wxCommandEvent& e) {
double slider_value = SLIDER_UNSCALE(linear_slider->GetValue());
linear_input->GetTextCtrl()->SetValue(wxString::Format("%.3f", slider_value));
m_linear_last = wxString::Format("%.3f", slider_value);
}));
linear_slider->Bind(wxEVT_LEFT_UP, ([this](wxMouseEvent& e) {
update_mesh_number_text();
e.Skip();
}));
bSizer->Add(linear_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING);
wxBoxSizer* angle_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText* angle_title = new wxStaticText(this,
wxID_ANY, _L("Angle Deflection") + ": ");
angle_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
angle_sizer->Add(angle_title, 0, wxALIGN_LEFT);
angle_sizer->AddStretchSpacer(1);
wxSlider* angle_slider = new wxSlider(this, wxID_ANY,
SLIDER_SCALE_10(get_angle_defletion()),
1, 100, wxDefaultPosition,
wxSize(SLIDER_WIDTH, SLIDER_HEIGHT),
wxSL_HORIZONTAL);
angle_sizer->Add(angle_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
auto angle_input = new ::TextInput(this, m_angle_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER);
angle_input->GetTextCtrl()->SetFont(Label::Body_12);
angle_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
angle_sizer->Add(angle_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5));
angle_input->Bind(wxEVT_KILL_FOCUS, ([this, angle_input](wxFocusEvent& e) {
wxString value = angle_input->GetTextCtrl()->GetValue();
if (validate_number_range(value, 0.01, 1)) {
m_angle_last = value;
update_mesh_number_text();
} else {
MessageDialog msg_dlg(nullptr, _L("Please input a valid value (0.01 < angle deflection < 1.0)"), wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
angle_input->GetTextCtrl()->SetValue(m_angle_last);
}
e.Skip();
}));
// textctrl bind slider
angle_input->Bind(wxEVT_TEXT, ([this, angle_slider, angle_input](wxCommandEvent& e) {
double slider_value_long;
int slider_value;
wxString value = angle_input->GetTextCtrl()->GetValue();
if (value.ToDouble(&slider_value_long)) {
slider_value = SLIDER_SCALE_10(slider_value_long);
if (slider_value >= angle_slider->GetMin() && slider_value <= angle_slider->GetMax()) {
angle_slider->SetValue(slider_value);
}
}
}));
angle_slider->Bind(wxEVT_SLIDER, ([this, angle_slider, angle_input](wxCommandEvent& e) {
double slider_value = SLIDER_UNSCALE_10(angle_slider->GetValue());
angle_input->GetTextCtrl()->SetValue(wxString::Format("%.2f", slider_value));
m_angle_last = wxString::Format("%.2f", slider_value);
}));
angle_slider->Bind(wxEVT_LEFT_UP, ([this](wxMouseEvent& e) {
update_mesh_number_text();
e.Skip();
}));
bSizer->Add(angle_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL);
m_split_compound_checkbox = new wxCheckBox(this, wxID_ANY, _L("Split compound and compsolid into multiple objects"), wxDefaultPosition, wxDefaultSize, 0);
m_split_compound_checkbox->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
m_split_compound_checkbox->SetValue(wxGetApp().app_config->get_bool("is_split_compound"));
check_sizer->Add(m_split_compound_checkbox, 0, wxALIGN_LEFT);
bSizer->Add(check_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
wxBoxSizer* mesh_face_number_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText *mesh_face_number_title = new wxStaticText(this, wxID_ANY, _L("Number of triangular facets") + ": ");
mesh_face_number_title->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
mesh_face_number_text = new wxStaticText(this, wxID_ANY, "0");
mesh_face_number_text->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
mesh_face_number_text->SetMinSize(wxSize(FromDIP(150), -1));
mesh_face_number_sizer->Add(mesh_face_number_title, 0, wxALIGN_LEFT);
mesh_face_number_sizer->Add(mesh_face_number_text, 0, wxALIGN_LEFT);
bSizer->Add(mesh_face_number_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING);
wxBoxSizer* bSizer_button = new wxBoxSizer(wxHORIZONTAL);
bSizer_button->SetMinSize(wxSize(FromDIP(100), -1));
m_checkbox = new wxCheckBox(this, wxID_ANY, _L("Don't show again"), wxDefaultPosition, wxDefaultSize, 0);
m_checkbox->SetForegroundColour(StateColor::darkModeColorFor(FONT_COLOR));
bSizer_button->Add(m_checkbox, 0, wxALIGN_LEFT | wxLEFT | wxALIGN_CENTER_VERTICAL, LEFT_RIGHT_PADING);
bSizer_button->AddStretchSpacer(1);
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
bSizer_button->Add(dlg_btns, 0, wxEXPAND);
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this, angle_input, linear_input](wxCommandEvent& e) {
stop_task();
if (validate_number_range(angle_input->GetTextCtrl()->GetValue(), 0.01, 1) &&
validate_number_range(linear_input->GetTextCtrl()->GetValue(), 0.001, 0.1)) {
if (m_checkbox->IsChecked()) {
wxGetApp().app_config->set_bool("enable_step_mesh_setting", false);
}
wxGetApp().app_config->set_bool("is_split_compound", m_split_compound_checkbox->GetValue());
wxGetApp().app_config->set("linear_defletion", float_to_string_decimal_point(get_linear_defletion(), 3));
wxGetApp().app_config->set("angle_defletion", float_to_string_decimal_point(get_angle_defletion(), 2));
EndModal(wxID_OK);
}
SetFocusIgnoringChildren();
});
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
stop_task();
EndModal(wxID_CANCEL);
});
bSizer->Add(bSizer_button, 1, wxEXPAND);
this->SetSizer(bSizer);
update_mesh_number_text();
this->Layout();
bSizer->Fit(this);
this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
SetFocusIgnoringChildren();
});
mesh_face_number_text->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
SetFocusIgnoringChildren();
});
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) {
stop_task();
EndModal(wxID_CANCEL);
});
wxGetApp().UpdateDlgDarkUI(this);
}
StepMeshDialog::~StepMeshDialog()
{
stop_task();
}
void StepMeshDialog::on_task_done(wxCommandEvent& event)
{
wxString text = event.GetString();
mesh_face_number_text->SetLabel(text);
if(m_task) {
if (m_task->joinable()) {
m_task->join();
delete m_task;
m_task = nullptr;
}
}
}
void StepMeshDialog::stop_task()
{
if(m_task) {
m_file.m_stop_mesh.store(true);
if (m_task->joinable()) {
m_task->join();
delete m_task;
m_task = nullptr;
}
m_file.m_stop_mesh.store(false);
}
}
void StepMeshDialog::update_mesh_number_text()
{
if ((m_last_linear == get_linear_defletion()) && (m_last_angle == get_angle_defletion()) && (m_mesh_number != 0))
return;
wxString newText = wxString::Format(_L("Calculating, please wait..."));
mesh_face_number_text->SetLabel(newText);
stop_task();
if (!m_task) {
m_task = new boost::thread(Slic3r::create_thread([this]() -> void {
m_mesh_number = m_file.get_triangle_num(get_linear_defletion(), get_angle_defletion());
if (m_mesh_number != 0) {
wxString number_text = wxString::Format("%d", m_mesh_number);
wxCommandEvent event(wxEVT_THREAD_DONE);
event.SetString(number_text);
wxPostEvent(this, event);
m_last_linear = get_linear_defletion();
m_last_angle = get_angle_defletion();
}
}));
}
}