Merge branch 'main' into refactor/printer-agent-interface

This commit is contained in:
Ian Chua
2026-08-05 13:16:47 +08:00
committed by GitHub
239 changed files with 10744 additions and 1430 deletions

View File

@@ -4756,7 +4756,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
deselect_all();
}
//BBS Select plate in this 3D canvas.
else if (evt.LeftUp() && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
// The left up may come from an ImGui window (e.g. a drag started on the gizmo floating window and released over the bed),
// in which case it must not be treated as a click on the plate, otherwise the gizmo would be closed (see deselect_all below).
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
{
int hover_idx = m_hover_plate_idxs.front();
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);

View File

@@ -1119,6 +1119,10 @@ public:
void set_mouse_as_dragging() { m_mouse.dragging = true; }
bool is_mouse_dragging() const { return m_mouse.dragging; }
// True when the current left up event comes from an ImGui window and was not processed by it
// (e.g. a drag that started on a gizmo floating window and was released over the 3D scene).
// Such a release is the end of an ImGui interaction, not a click on the scene.
bool is_mouse_left_up_ignored() const { return m_mouse.ignore_left_up; }
double get_size_proportional_to_max_bed_size(double factor) const;

View File

@@ -256,18 +256,18 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
}
}
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts)
{
wxGetApp().CallAfter([=] {
ErrorDialog msg(parent, message, monospaced_font);
ErrorDialog msg(parent, message, has_code_excerpts);
msg.ShowModal();
});
}
void show_error(wxWindow* parent, const char* message, bool monospaced_font)
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts)
{
assert(message);
show_error(parent, wxString::FromUTF8(message), monospaced_font);
show_error(parent, wxString::FromUTF8(message), has_code_excerpts);
}
void show_error_id(int id, const std::string& message)

View File

@@ -40,11 +40,11 @@ extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts = false);
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts = false);
inline void show_error(wxWindow* parent, const std::string& message, bool has_code_excerpts = false) { show_error(parent, message.c_str(), has_code_excerpts); }
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);

View File

@@ -113,14 +113,7 @@ public:
update_dark_ui(this);
#endif
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
update_em_unit();
// recalc_font();
@@ -235,6 +228,19 @@ private:
// m_em_unit = metrics.averageWidth;
// }
// update em_unit value for new window font
void update_em_unit()
{
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
}
// check if new scale is differ from previous
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
@@ -247,8 +253,7 @@ private:
// set normal application font as a current window font
m_normal_font = this->GetFont();
// update em_unit value for new window font
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
update_em_unit();
// rescale missed controls sizes and images
on_dpi_changed(suggested_rect);

View File

@@ -566,8 +566,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event)
{
static bool was_dragging = true;
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) {
static bool was_dragging = true;
// The left up may be the end of a drag that started on the gizmo floating window (e.g. selecting
// text in the input field). Such a release is not a click on the scene and must not close the gizmo.
// (The flag is only set for left up events, so right up behavior is unchanged.)
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging && !m_parent.is_mouse_left_up_ignored()) {
// is hovered volume closest hovered?
int hovered_idx = m_parent.get_first_hover_volume_idx();
if (hovered_idx < 0)

View File

@@ -9,8 +9,13 @@
#include <wx/clipbrd.h>
#include <wx/checkbox.h>
#include <wx/html/htmlwin.h>
#include <wx/html/winpars.h>
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
@@ -229,12 +234,82 @@ void MsgDialog::finalize()
}
// A placeholder-parser caret line, pointing at the column where parsing failed.
static bool is_caret_line(const std::string &line)
{
return std::count(line.begin(), line.end(), '^') == 1 &&
std::all_of(line.begin(), line.end(), [](char c) { return c == ' ' || c == '^'; });
}
// Tag each line as a code excerpt (a caret line or the source line above one) that must stay
// monospaced for the '^' to align.
static std::vector<std::pair<std::string, bool>> classify_code_lines(const std::string &msg)
{
std::vector<std::string> lines;
boost::split(lines, msg, boost::is_any_of("\n"));
for (std::string &line : lines)
if (!line.empty() && line.back() == '\r')
line.pop_back();
std::vector<std::pair<std::string, bool>> tagged;
tagged.reserve(lines.size());
for (size_t i = 0; i < lines.size(); ++i) {
bool is_code = is_caret_line(lines[i]) || (i + 1 < lines.size() && is_caret_line(lines[i + 1]));
tagged.emplace_back(std::move(lines[i]), is_code);
}
return tagged;
}
// Keeps whitespace literal so the caret's leading spaces survive.
// Used inside <code>, which supplies the fixed face. <pre> does both but adds a blank line above it.
class CodeExcerptTagHandler : public wxHtmlWinTagHandler
{
public:
wxString GetSupportedTags() override { return wxT("EXCERPT"); }
bool HandleTag(const wxHtmlTag &tag) override
{
const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
ParseInner(tag);
m_WParser->SetWhitespaceMode(ws);
return true;
}
};
// Render the message as HTML, monospacing only the code excerpts.
static std::string format_parser_error_html(const std::string &msg)
{
std::string out;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
if (!out.empty()) out += "<br>"; // join, not trail; a trailing <br> forces a scrollbar
std::string escaped = xml_escape(text);
if (is_code)
out += "<code><excerpt>" + escaped + "</excerpt></code>";
else
out += escaped;
}
return out;
}
// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
{
wxClientDC dc(parent);
int width = 0, height = 0;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
dc.SetFont(is_code ? code_font : prose_font);
width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
height += dc.GetCharHeight();
}
return wxSize(width, height);
}
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
static void add_msg_content(wxWindow *parent,
wxBoxSizer *content_sizer,
wxString msg,
bool monospaced_font = false,
bool is_marked_msg = false,
bool has_code_excerpts = false,
bool is_marked_msg = false,
const wxString &link_text = "",
std::function<void(const wxString &)> link_callback = nullptr)
{
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
// count lines in the message
int msg_lines = 0;
if (!monospaced_font) {
if (!has_code_excerpts) {
int line_len = 55;// count of symbols in one line
int start_line = 0;
for (auto i = msg.begin(); i != msg.end(); ++i) {
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
page_size = wxSize(info_width, page_height);
}
else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
wxSize msg_sz;
if (has_code_excerpts) {
msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
} else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
msg_sz = dc.GetMultiLineTextExtent(msg);
}
msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
int page_height = msg_sz.GetY();
// Reserve the horizontal scrollbar's height, or it clips the last line.
if (msg_sz.GetX() > info_width)
page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
// Extra line breaks in message dialog
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
html->Destroy();
if (msg_sz.GetX() < info_width) {//No need for line breaks
info_width = msg_sz.GetX();
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
}
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
if (monospaced_font)
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
std::string msg_escaped;
if (has_code_excerpts) {
html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
msg_escaped = format_parser_error_html(msg.ToUTF8().data());
} else {
msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
}
if (!link_text.IsEmpty() && link_callback) {
msg_escaped += "<span><a href=\"#\" style=\"color:rgb(0, 150, 136); text-decoration:underline;\">" + std::string(link_text.ToUTF8().data()) + "</a></span>";
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
, msg(temp_msg)
{
add_msg_content(this, content_sizer, msg, monospaced_font);
add_msg_content(this, content_sizer, msg, has_code_excerpts);
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
// Use a small bitmap for code excerpts, which cannot wrap and so need the width.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
SetMaxSize(MSG_DLG_MAX_SIZE);

View File

@@ -106,9 +106,9 @@ protected:
class ErrorDialog : public MsgDialog
{
public:
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;

View File

@@ -3078,7 +3078,7 @@ void TabPrint::build()
optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims");
optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle");
optgroup->append_single_option_line("brim_ears_detection_length", "others_settings_brim#ear-detection-radius");
optgroup->append_single_option_line("brim_ears_outer_only");
optgroup->append_single_option_line("brim_ears_outer_only", "others_settings_brim#brim-ears-outer-only");
optgroup = page->new_optgroup(L("Special mode"), L"param_special");
optgroup->append_single_option_line("slicing_mode", "others_settings_special_mode#slicing-mode");
@@ -4007,13 +4007,12 @@ void TabFilament::add_filament_overrides_page()
const int extruder_idx = 0; // #ys_FIXME
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
auto append_retraction_option = [this, retraction_optgroup](const std::string& opt_key, int opt_index)
auto append_retraction_option = [this](ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index)
{
Line line {"",""};
line = retraction_optgroup->create_single_option_line(retraction_optgroup->get_option(opt_key, opt_index));
line = optgroup->create_single_option_line(optgroup->get_option(opt_key, opt_index));
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(retraction_optgroup), opt_key, opt_index](wxWindow* parent) {
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
auto check_box = new ::CheckBox(parent); // ORCA modernize checkboxes
check_box->Bind(wxEVT_TOGGLEBUTTON, [this, optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
const bool is_checked = evt.IsChecked();
@@ -4040,9 +4039,10 @@ void TabFilament::add_filament_overrides_page()
return check_box;
};
retraction_optgroup->append_line(line);
optgroup->append_line(line);
};
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
for (const std::string opt_key : { "filament_retraction_length",
"filament_z_hop",
"filament_z_hop_types",
@@ -4066,7 +4066,13 @@ void TabFilament::add_filament_overrides_page()
//SoftFever
// "filament_seam_gap"
})
append_retraction_option(opt_key, extruder_idx);
append_retraction_option(retraction_optgroup, opt_key, extruder_idx);
ConfigOptionsGroupShp toolchange_optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction_material_change");
for (const std::string opt_key : { "filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange"
})
append_retraction_option(toolchange_optgroup, opt_key, extruder_idx);
ConfigOptionsGroupShp ironing_optgroup = page->new_optgroup(L("Ironing"), L"param_ironing");
auto append_ironing_option = [this, ironing_optgroup](const std::string& opt_key, int opt_index)
@@ -4181,6 +4187,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
"filament_retraction_speed",
"filament_deretraction_speed",
"filament_retract_restart_extra",
"filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange",
"filament_retraction_minimum_travel",
"filament_retract_when_changing_layer",
"filament_wipe",
@@ -4211,7 +4219,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
is_checked &= !dynamic_cast<ConfigOptionVectorBase*>(m_config->option(opt_key))->is_nil(extruder_idx);
m_overrides_options[opt_key]->SetValue(is_checked);
Field* field = optgroup->get_fieldc(opt_key, 0);
// the toolchange overrides live in their own optgroup, so search the whole page
Field* field = page->get_field(opt_key, 0);
if (field == nullptr) continue;
if (opt_key == "filament_long_retractions_when_cut") {
@@ -5017,6 +5026,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("gcode_skip_config_block", "printer_basic_information_advanced#skip-g-code-config-block");
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");