mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
Merge branch 'main' into feature/texture_displacement
This commit is contained in:
+64
-14
@@ -70,6 +70,9 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
|
||||
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
|
||||
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
|
||||
float FULL_BLACK_THRESHOLD = 0.2f;
|
||||
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
|
||||
// shadow/environment samplers when realistic view is disabled.
|
||||
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
|
||||
|
||||
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
|
||||
{
|
||||
@@ -518,6 +521,37 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
// render the outline using depth buffer and discard the pixels that are not on the outline
|
||||
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
|
||||
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
|
||||
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
|
||||
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
|
||||
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
|
||||
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
|
||||
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
|
||||
shader->get_uniform_location("msaa_samples") >= 0;
|
||||
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
|
||||
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
|
||||
// the case with realistic view off - and GL forbids two sampler types referring to the same image
|
||||
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
|
||||
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
|
||||
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
|
||||
int aa_samples = 1;
|
||||
if (use_msaa_outline) {
|
||||
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
|
||||
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
|
||||
if (value == "2" || value == "4" || value == "8" || value == "16")
|
||||
aa_samples = ::atoi(value.c_str());
|
||||
}
|
||||
// Never request more samples than the driver supports for depth textures (a 1-sample texture
|
||||
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
|
||||
GLint max_samples = 1;
|
||||
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
|
||||
if (aa_samples > max_samples)
|
||||
aa_samples = max_samples < 1 ? 1 : max_samples;
|
||||
if (aa_samples < 1)
|
||||
aa_samples = 1;
|
||||
}
|
||||
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
||||
GLuint depth_fbo = 0;
|
||||
GLuint depth_tex = 0;
|
||||
@@ -525,21 +559,26 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glGenFramebuffers(1, &depth_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
if (use_msaa_outline) {
|
||||
// Multisample textures do not take filter/wrap parameters.
|
||||
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
|
||||
} else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
}
|
||||
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
|
||||
} else {
|
||||
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
@@ -550,12 +589,15 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
|
||||
// to a sampled unit would be a feedback loop.
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
@@ -565,13 +607,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
}
|
||||
shader->set_uniform("is_outline", true);
|
||||
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
shader->set_uniform("depth_tex", 0);
|
||||
shader->set_uniform("msaa_samples", aa_samples);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("depth_tex", depth_tex_unit);
|
||||
simple_render(shader, model_objects, colors);
|
||||
|
||||
// Some clean up to do
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("is_outline", false);
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
@@ -1075,6 +1121,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
|
||||
const float support_normal_z = get_selection_support_normal_z();
|
||||
|
||||
// Prime depth_tex on every frame so non-outline draws do not keep the
|
||||
// default sampler unit 0, which can conflict with other sampler types.
|
||||
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
if (type == ERenderType::Transparent) {
|
||||
|
||||
@@ -736,7 +736,7 @@ wxBoxSizer* AMSDryCtrWin::create_guide_right_section(wxPanel* parent)
|
||||
|
||||
m_back_button = create_button(
|
||||
parent,
|
||||
wxString::FromUTF8(_CTX_utf8(L_CONTEXT("Back", "amsdrying"), "amsdrying")),
|
||||
wxString::FromUTF8(_u8L_CONTEXT(L_CONTEXT("Back", "amsdrying"), "amsdrying")),
|
||||
wxColour("#F8F8F8"), // Background color - light gray
|
||||
wxColour("#D0D0D0"), // Border color - gray
|
||||
*wxBLACK // Text color - black
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include <algorithm>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/dcgraph.h>
|
||||
@@ -1075,54 +1076,105 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
||||
|
||||
// Sort the filaments
|
||||
{
|
||||
static std::unordered_map<wxString, int> sorted_names
|
||||
{ {"Bambu PLA Basic", 0},
|
||||
{"Bambu PLA Matte", 1},
|
||||
{"Bambu PETG HF", 2},
|
||||
{"Bambu ABS", 3},
|
||||
{"Bambu PLA Silk", 4},
|
||||
{"Bambu PLA-CF" , 5},
|
||||
{"Bambu PLA Galaxy", 6},
|
||||
{"Bambu PLA Metal", 7},
|
||||
{"Bambu PLA Marble", 8},
|
||||
{"Bambu PETG-CF", 9},
|
||||
{"Bambu PETG Translucent", 10},
|
||||
{"Bambu ABS-GF", 11}
|
||||
std::unordered_map<wxString, int> selected_filament_ranks;
|
||||
|
||||
// Helper lambda to find a filament Preset by name. We can call this multiple times to walk the inheritance chain and find the base filament.
|
||||
auto find_filament_by_name = [](const std::string& wanted, const PresetCollection& filaments) -> const Preset* {
|
||||
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
|
||||
if (it->name == wanted) {
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
static std::vector<wxString> sorted_vendors { "Bambu Lab", "Generic" };
|
||||
static std::vector<wxString> sorted_types { "PLA", "PETG", "ABS", "TPU" };
|
||||
auto _filament_sorter = [&query_filament_vendors, &query_filament_types](const wxString& left, const wxString& right) -> bool
|
||||
{
|
||||
{ // Compare name order
|
||||
const auto& iter1 = sorted_names.find(left);
|
||||
int name_order1 = (iter1 != sorted_names.end()) ? iter1->second : INT_MAX;
|
||||
// For each active filament preset, find its base filament alias and promote it in extruder order.
|
||||
auto bundle = wxGetApp().preset_bundle;
|
||||
const auto& preset_names = bundle->filament_presets;
|
||||
for (size_t i = preset_names.size(); i-- > 0; ) {
|
||||
std::string wanted = preset_names[i];
|
||||
const int sort_rank = -static_cast<int>(preset_names.size() - i);
|
||||
|
||||
const Preset* match = nullptr;
|
||||
|
||||
const auto& iter2 = sorted_names.find(right);
|
||||
int name_order2 = (iter2 != sorted_names.end()) ? iter2->second : INT_MAX;
|
||||
if (name_order1 != name_order2)
|
||||
do {
|
||||
auto find_result = find_filament_by_name(wanted, bundle->filaments);
|
||||
if (!find_result) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " No available filament name matches " << wanted;
|
||||
break;
|
||||
}
|
||||
|
||||
match = find_result;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Found available filament matching current preset name " << wanted
|
||||
<< " - Name: " << match->name << " - Alias: " << match->alias
|
||||
<< " - Inherits: " << match->inherits();
|
||||
|
||||
if (match->inherits().length() == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " No more inherits so we reached the base filament";
|
||||
break;
|
||||
}
|
||||
|
||||
wanted = match->inherits();
|
||||
} while (1); // Or loop while (match->alias.length() == 0) because existence of alias and inherits on a Preset seem to be exclusive
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Update filament rank to " + std::to_string(sort_rank) + " for preset Name: "
|
||||
<< match->name << " - Alias: " << match->alias;
|
||||
selected_filament_ranks.insert_or_assign(match->alias, sort_rank);
|
||||
}
|
||||
|
||||
static const std::vector<wxString> sorted_vendors { "Generic" };
|
||||
static const std::vector<wxString> sorted_types { "PLA", "PETG", "ABS", "TPU" };
|
||||
auto priority_rank = [](const std::vector<wxString>& priorities, const wxString& value) {
|
||||
const auto iter = std::find_if(priorities.begin(), priorities.end(), [&value](const wxString& priority) {
|
||||
return priority.CmpNoCase(value) == 0;
|
||||
});
|
||||
return iter - priorities.begin();
|
||||
};
|
||||
auto _filament_sorter = [&query_filament_vendors, &query_filament_types, &selected_filament_ranks, &priority_rank](const wxString& left, const wxString& right) -> bool
|
||||
{
|
||||
{ // Compare selected filament order
|
||||
const auto& iter1 = selected_filament_ranks.find(left);
|
||||
int selected_order1 = (iter1 != selected_filament_ranks.end()) ? iter1->second : INT_MAX;
|
||||
|
||||
const auto& iter2 = selected_filament_ranks.find(right);
|
||||
int selected_order2 = (iter2 != selected_filament_ranks.end()) ? iter2->second : INT_MAX;
|
||||
if (selected_order1 != selected_order2)
|
||||
{
|
||||
return name_order1 < name_order2;
|
||||
return selected_order1 < selected_order2;
|
||||
}
|
||||
}
|
||||
{ // Compare vendor
|
||||
auto iter1 = std::find(sorted_vendors.begin(), sorted_vendors.end(), query_filament_vendors[left]);
|
||||
auto iter2 = std::find(sorted_vendors.begin(), sorted_vendors.end(), query_filament_vendors[right]);
|
||||
if (iter1 != iter2)
|
||||
{
|
||||
return iter1 < iter2;
|
||||
};
|
||||
const wxString& vendor1 = query_filament_vendors.at(left);
|
||||
const wxString& vendor2 = query_filament_vendors.at(right);
|
||||
const auto rank1 = priority_rank(sorted_vendors, vendor1);
|
||||
const auto rank2 = priority_rank(sorted_vendors, vendor2);
|
||||
if (rank1 != rank2)
|
||||
return rank1 < rank2;
|
||||
|
||||
const int vendor_compare = vendor1.CmpNoCase(vendor2);
|
||||
if (vendor_compare != 0)
|
||||
return vendor_compare < 0;
|
||||
}
|
||||
{ // Compare type
|
||||
auto iter1 = std::find(sorted_types.begin(), sorted_types.end(), query_filament_types[left]);
|
||||
auto iter2 = std::find(sorted_types.begin(), sorted_types.end(), query_filament_types[right]);
|
||||
if (iter1 != iter2)
|
||||
{
|
||||
return iter1 < iter2;
|
||||
}
|
||||
const wxString& type1 = query_filament_types.at(left);
|
||||
const wxString& type2 = query_filament_types.at(right);
|
||||
const auto rank1 = priority_rank(sorted_types, type1);
|
||||
const auto rank2 = priority_rank(sorted_types, type2);
|
||||
if (rank1 != rank2)
|
||||
return rank1 < rank2;
|
||||
|
||||
const int type_compare = type1.CmpNoCase(type2);
|
||||
if (type_compare != 0)
|
||||
return type_compare < 0;
|
||||
}
|
||||
|
||||
return left < right;
|
||||
const int name_compare = left.CmpNoCase(right);
|
||||
return name_compare != 0 ? name_compare < 0 : left < right;
|
||||
};
|
||||
|
||||
std::sort(filament_items.begin(), filament_items.end(), _filament_sorter);
|
||||
|
||||
@@ -2100,7 +2100,7 @@ void AmsReplaceMaterialDialog::create()
|
||||
m_top_line->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
m_main_sizer->Add(m_top_line, 0, wxEXPAND, 0);
|
||||
|
||||
m_nozzle_btn_panel = new SwitchBoard(this, _L("Left"), _L("Right"), wxSize(FromDIP(126), FromDIP(26)));
|
||||
m_nozzle_btn_panel = new SwitchBoard(this, _L_CONTEXT("Left", "Nozzle position"), _L_CONTEXT("Right", "Nozzle position"), wxSize(FromDIP(126), FromDIP(26)));
|
||||
m_nozzle_btn_panel->Hide();
|
||||
m_nozzle_btn_panel->Connect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(AmsReplaceMaterialDialog::on_nozzle_selected), NULL, this);
|
||||
|
||||
|
||||
@@ -869,11 +869,11 @@ void AuxiliaryPanel::init_tabpanel()
|
||||
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
||||
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
||||
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), "", true);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), "", false);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), "", false);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), "", false);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), "", false);
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
|
||||
}
|
||||
|
||||
wxWindow *AuxiliaryPanel::create_side_tools()
|
||||
|
||||
@@ -192,7 +192,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
|
||||
|
||||
|
||||
|
||||
SetSizer(sizer_main);
|
||||
SetSizerAndFit(sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
@@ -670,7 +670,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
m_sizer_main->Add(m_sw_bind_failed_info, 0, wxALIGN_CENTER, 0);
|
||||
m_sizer_main->Add(m_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
@@ -992,7 +992,7 @@ UnBindMachineDialog::UnBindMachineDialog(Plater *plater /*= nullptr*/)
|
||||
m_sizer_main->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT, ButtonProps::ChoiceButtonGap());
|
||||
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
|
||||
@@ -632,9 +632,8 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
|
||||
|
||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetSizerAndFit(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -910,9 +909,8 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
|
||||
|
||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetSizerAndFit(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
@@ -162,7 +162,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
|
||||
body_panel->Layout();
|
||||
|
||||
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
|
||||
@@ -488,7 +488,6 @@ void CalibrationPanel::init_tabpanel() {
|
||||
selected = true;
|
||||
m_tabpanel->AddPage(m_cali_panels[i],
|
||||
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
|
||||
"",
|
||||
selected);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,9 +112,8 @@ CloneDialog::CloneDialog(wxWindow *parent)
|
||||
|
||||
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
|
||||
|
||||
this->SetSizer(v_sizer);
|
||||
this->SetSizerAndFit(v_sizer);
|
||||
this->Layout();
|
||||
v_sizer->Fit(this);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "libslic3r/GCode/AdaptivePAProcessor.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
@@ -70,6 +71,12 @@ void ConfigManipulation::toggle_line(const std::string& opt_key, const bool togg
|
||||
cb_toggle_line(opt_key, toggle, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::set_option_label(const std::string& opt_key, const wxString& label, int opt_index)
|
||||
{
|
||||
if (cb_set_option_label)
|
||||
cb_set_option_label(opt_key, label, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
|
||||
if (is_msg_dlg_already_exist)
|
||||
return;
|
||||
@@ -244,6 +251,59 @@ void ConfigManipulation::check_chamber_minimal_temperature(DynamicPrintConfig* c
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManipulation::layer_height_limits(double& min_layer_height, double& max_layer_height) const
|
||||
{
|
||||
const DynamicPrintConfig& printer_config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
const std::vector<double>& min_limits = printer_config.option<ConfigOptionFloats>("min_layer_height")->values;
|
||||
const std::vector<double>& max_limits = printer_config.option<ConfigOptionFloats>("max_layer_height")->values;
|
||||
min_layer_height = *std::min_element(min_limits.begin(), min_limits.end());
|
||||
max_layer_height = *std::max_element(max_limits.begin(), max_limits.end());
|
||||
}
|
||||
|
||||
bool ConfigManipulation::check_layer_height(DynamicPrintConfig* config)
|
||||
{
|
||||
double min_layer_height = 0., max_layer_height = 0.;
|
||||
layer_height_limits(min_layer_height, max_layer_height);
|
||||
const double layer_height = config->opt_float("layer_height");
|
||||
|
||||
if (min_layer_height > EPSILON && layer_height < EPSILON) {
|
||||
const wxString msg_text = wxString::Format(_L("Layer height is too small. It will be set to the minimum (%g mm)."), min_layer_height);
|
||||
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK);
|
||||
dialog.SetButtonLabel(wxID_OK, _L("OK"));
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
is_msg_dlg_already_exist = false;
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
new_conf.set_key_value("layer_height", new ConfigOptionFloat(min_layer_height));
|
||||
apply(config, &new_conf);
|
||||
return true;
|
||||
}
|
||||
if (max_layer_height > EPSILON && layer_height > max_layer_height + EPSILON)
|
||||
return layer_height_out_of_range_dialog(config, max_layer_height);
|
||||
if (min_layer_height > EPSILON && layer_height < min_layer_height - EPSILON)
|
||||
return layer_height_out_of_range_dialog(config, min_layer_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConfigManipulation::layer_height_out_of_range_dialog(DynamicPrintConfig* config, double clamp_to)
|
||||
{
|
||||
wxString msg_text = _(L("Layer height is outside the limits set in Printer Settings -> Extruder -> Layer height limits, "
|
||||
"this may cause printing quality issues."));
|
||||
msg_text += "\n\n" + wxString::Format(_L("Adjust it to the limit (%g mm) automatically?"), clamp_to);
|
||||
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO);
|
||||
dialog.SetButtonLabel(wxID_YES, _L("Adjust"));
|
||||
dialog.SetButtonLabel(wxID_NO, _L("Ignore"));
|
||||
is_msg_dlg_already_exist = true;
|
||||
const bool adjust = dialog.ShowModal() == wxID_YES;
|
||||
if (adjust) {
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
new_conf.set_key_value("layer_height", new ConfigOptionFloat(clamp_to));
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
is_msg_dlg_already_exist = false;
|
||||
return adjust;
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config, const bool is_plate_config)
|
||||
{
|
||||
// #ys_FIXME_to_delete
|
||||
@@ -258,7 +318,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
|
||||
// layer_height shouldn't be equal to zero
|
||||
auto layer_height = config->opt_float("layer_height");
|
||||
auto gpreset = GUI::wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
if (layer_height < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Layer height too small\nIt has been reset to 0.2"));
|
||||
@@ -271,20 +330,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
|
||||
//BBS: limite the max layer_herght
|
||||
auto max_lh = gpreset.config.opt_float("max_layer_height",0);
|
||||
if (max_lh > 0.2 && layer_height > max_lh+ EPSILON)
|
||||
{
|
||||
const wxString msg_text = wxString::Format(L"Too large layer height.\nReset to %0.3f.", max_lh);
|
||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
new_conf.set_key_value("layer_height", new ConfigOptionFloat(max_lh));
|
||||
apply(config, &new_conf);
|
||||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
|
||||
//BBS: ironing_spacing shouldn't be too small or equal to zero
|
||||
if (config->opt_float("ironing_spacing") < 0.05)
|
||||
{
|
||||
@@ -703,12 +748,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
|
||||
toggle_line("spiral_starting_flow_ratio", has_spiral_vase);
|
||||
toggle_line("spiral_finishing_flow_ratio", has_spiral_vase);
|
||||
bool has_top_shell = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_top_shell_layers = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell || has_bottom_shell;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_line("sparse_infill_smooth_factor", is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
toggle_field("bottom_surface_density", has_bottom_shell);
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
@@ -751,7 +798,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
for (auto el : { "sparse_infill_speed", "bridge_speed", "internal_bridge_speed"})
|
||||
toggle_field(el, have_infill || has_solid_infill, variant_index);
|
||||
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell);
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell_layers);
|
||||
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_shell);
|
||||
|
||||
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
|
||||
@@ -806,14 +853,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
|
||||
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
|
||||
|
||||
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
|
||||
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
|
||||
const bool have_auto_brim_ear = brim_type == btEar;
|
||||
const bool have_painted_brim_ear = brim_type == btPainted;
|
||||
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
|
||||
const auto brim_width = config->opt_float("brim_width");
|
||||
// disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0
|
||||
// Automatic brim ear settings require a non-zero brim width.
|
||||
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
|
||||
toggle_field("brim_ears_detection_length", brim_width > 0.0f);
|
||||
// hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected
|
||||
toggle_line("brim_ears_max_angle", have_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_brim_ear);
|
||||
// Painted ears carry their own radius and do not depend on brim_width.
|
||||
toggle_field("brim_ears_outer_only", have_painted_brim_ear || brim_width > 0.0f);
|
||||
toggle_line("brim_ears_max_angle", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_outer_only", have_auto_brim_ear || have_painted_brim_ear);
|
||||
|
||||
// Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled
|
||||
toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0 || config->option<ConfigOptionPercent>("elefant_foot_layers_density")->get_abs_value(1.0f) < 1.0f);
|
||||
@@ -1008,7 +1060,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
||||
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
||||
|
||||
toggle_line("min_width_top_surface", config->opt_bool("only_one_wall_top") || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
// Orca: the one-wall options act on top/bottom surfaces, which exist only with a shell. An unfilled surface
|
||||
// (0% surface density) is still a surface, so these are gated on the layer counts alone.
|
||||
toggle_line("only_one_wall_first_layer", has_bottom_shell);
|
||||
toggle_line("only_one_wall_top", has_top_shell_layers);
|
||||
toggle_line("min_width_top_surface", (has_top_shell_layers && config->opt_bool("only_one_wall_top")) || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
|
||||
for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "hole_to_polyhole_max_edges" })
|
||||
toggle_line(el, config->opt_bool("hole_to_polyhole"));
|
||||
|
||||
@@ -29,6 +29,7 @@ class ConfigManipulation
|
||||
std::function<void()> load_config = nullptr;
|
||||
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
|
||||
std::function<void(const std::string &, bool toggle, int opt_index)> cb_toggle_line = nullptr;
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr;
|
||||
// callback to propagation of changed value, if needed
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
|
||||
//BBS: change local config to const DynamicPrintConfig
|
||||
@@ -45,10 +46,12 @@ public:
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change,
|
||||
//BBS: change local config to DynamicPrintConfig
|
||||
const DynamicPrintConfig* local_config = nullptr,
|
||||
wxWindow* msg_dlg_parent = nullptr) :
|
||||
wxWindow* msg_dlg_parent = nullptr,
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr) :
|
||||
load_config(load_config),
|
||||
cb_toggle_field(cb_toggle_field),
|
||||
cb_toggle_line(cb_toggle_line),
|
||||
cb_set_option_label(cb_set_option_label),
|
||||
cb_value_change(cb_value_change),
|
||||
m_msg_dlg_parent(msg_dlg_parent),
|
||||
local_config(local_config) {}
|
||||
@@ -58,6 +61,7 @@ public:
|
||||
load_config = nullptr;
|
||||
cb_toggle_field = nullptr;
|
||||
cb_toggle_line = nullptr;
|
||||
cb_set_option_label = nullptr;
|
||||
cb_value_change = nullptr;
|
||||
}
|
||||
|
||||
@@ -67,6 +71,7 @@ public:
|
||||
t_config_option_keys const &applying_keys() const;
|
||||
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
|
||||
|
||||
// FFF print
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
|
||||
@@ -81,6 +86,9 @@ public:
|
||||
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
|
||||
void check_chamber_temperature(DynamicPrintConfig* config);
|
||||
void check_chamber_minimal_temperature(DynamicPrintConfig* config);
|
||||
bool check_layer_height(DynamicPrintConfig* config);
|
||||
bool layer_height_out_of_range_dialog(DynamicPrintConfig* config, double clamp_to);
|
||||
void layer_height_limits(double& min_layer_height, double& max_layer_height) const;
|
||||
void set_is_BBL_Printer(bool is_bbl_printer) { is_BBL_Printer = is_bbl_printer; };
|
||||
bool get_is_BBL_Printer() { return is_BBL_Printer; };
|
||||
// SLA print
|
||||
|
||||
@@ -80,9 +80,8 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
|
||||
|
||||
main_sizer->Add(sizer_top);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
CentreOnParent();
|
||||
|
||||
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
|
||||
@@ -157,6 +156,8 @@ void ConnectPrinterDialog::on_input_enter(wxCommandEvent& evt)
|
||||
void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
{
|
||||
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
||||
if (code.empty())
|
||||
code = "88888888";
|
||||
for (char c : code) {
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||
show_error(this, _L("Invalid input"));
|
||||
@@ -164,7 +165,7 @@ void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
}
|
||||
}
|
||||
if (m_obj) {
|
||||
m_obj->set_user_access_code(code.ToStdString());
|
||||
m_obj->set_access_code(code.ToStdString());
|
||||
}
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace {
|
||||
// Orca: access_code and user_access_code used to be separate AppConfig keys before the two
|
||||
// fields were merged; fall back to the legacy key so existing users' saved codes aren't lost.
|
||||
std::string get_access_code_with_legacy_fallback(Slic3r::AppConfig* config, const std::string& dev_id)
|
||||
{
|
||||
std::string code = config->get("access_code", dev_id);
|
||||
if (code.empty())
|
||||
code = config->get("user_access_code", dev_id);
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
DeviceManager::DeviceManager(NetworkAgent* agent)
|
||||
@@ -48,8 +60,7 @@ namespace Slic3r
|
||||
obj->bind_sec_link = "secure";
|
||||
obj->m_is_online = true;
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(config->get("access_code", m.dev_id), false);
|
||||
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, m.dev_id), false);
|
||||
if (obj->has_access_right()) {
|
||||
localMachineList.insert(std::make_pair(m.dev_id, obj));
|
||||
} else {
|
||||
@@ -339,8 +350,7 @@ namespace Slic3r
|
||||
//load access code
|
||||
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
obj->set_access_code(Slic3r::GUI::wxGetApp().app_config->get("access_code", dev_id), false);
|
||||
obj->set_user_access_code(Slic3r::GUI::wxGetApp().app_config->get("user_access_code", dev_id), false);
|
||||
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id), false);
|
||||
}
|
||||
localMachineList.insert(std::make_pair(dev_id, obj));
|
||||
|
||||
@@ -382,7 +392,6 @@ namespace Slic3r
|
||||
obj->m_is_online = true;
|
||||
obj->last_alive = Slic3r::Utils::get_current_time_utc();
|
||||
obj->set_access_code(access_code, false);
|
||||
obj->set_user_access_code(access_code, false);
|
||||
|
||||
update_local_machine(*obj);
|
||||
|
||||
@@ -496,6 +505,26 @@ namespace Slic3r
|
||||
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::clear_other_devices()
|
||||
{
|
||||
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
|
||||
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
|
||||
const auto my = get_my_machine_list();
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end();)
|
||||
{
|
||||
if (my.find(it->first) == my.end())
|
||||
{
|
||||
// not a "My Device" -> an "Other Device"
|
||||
delete it->second;
|
||||
it = localMachineList.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceManager::set_selected_machine(std::string dev_id)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
|
||||
@@ -591,6 +620,7 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
selected_machine = dev_id;
|
||||
record_user_last_machine(selected_machine);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -849,26 +879,44 @@ namespace Slic3r
|
||||
int result = m_agent->get_user_print_info(&http_code, &body, provider);
|
||||
if (result == 0)
|
||||
{
|
||||
parse_user_print_info(body);
|
||||
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
|
||||
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
|
||||
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::record_user_last_machine(const std::string& dev_id)
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_user_last_machine() const
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
|
||||
if (!user_last_machine.empty()) {
|
||||
return user_last_machine;
|
||||
} else if (m_agent) {
|
||||
return m_agent->get_user_selected_machine();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void DeviceManager::load_last_machine()
|
||||
{
|
||||
// Get all available machines, include cloud machines and lan machines that have access right
|
||||
auto all_machines = get_my_machine_list();
|
||||
if (all_machines.empty())
|
||||
// Only reconnect the remembered cloud machine. Do not select an arbitrary
|
||||
// first machine: agent swaps intentionally leave the selection empty until
|
||||
// the new agent explicitly selects its configured printer.
|
||||
if (userMachineList.empty())
|
||||
return;
|
||||
|
||||
// Then connect to the machine we last selected if available
|
||||
const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : "";
|
||||
const auto last_machine = all_machines.find(last_monitor_machine);
|
||||
if (last_machine != all_machines.end()) {
|
||||
this->set_selected_machine(last_machine->second->get_dev_id());
|
||||
} else {
|
||||
// If not, then select the first available one
|
||||
this->set_selected_machine(all_machines.begin()->second->get_dev_id());
|
||||
}
|
||||
|
||||
const auto& last_monitor_machine = get_user_last_machine();
|
||||
if (userMachineList.find(last_monitor_machine) != userMachineList.end())
|
||||
set_selected_machine(last_monitor_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state)
|
||||
|
||||
@@ -48,6 +48,13 @@ public:
|
||||
MachineObject* get_selected_machine();
|
||||
bool set_selected_machine(std::string dev_id);
|
||||
|
||||
// why: clears stale sidebar sync-status / AMS visuals. Public so the printer-agent
|
||||
// swap path can reuse it instead of duplicating the two sidebar calls.
|
||||
void OnSelectedMachineLost();
|
||||
|
||||
void record_user_last_machine(const std::string& dev_id);
|
||||
std::string get_user_last_machine() const;
|
||||
|
||||
// local machine
|
||||
void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; };
|
||||
MachineObject* get_local_selected_machine() const { return get_local_machine(local_selected_machine); }
|
||||
@@ -67,6 +74,8 @@ public:
|
||||
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
|
||||
void clean_user_info(bool keep_local_selection = false);
|
||||
|
||||
void clear_other_devices();
|
||||
|
||||
void load_last_machine();
|
||||
void update_user_machine_list_info(const std::string& provider);
|
||||
void parse_user_print_info(std::string body);
|
||||
@@ -107,7 +116,6 @@ private:
|
||||
void check_pushing();
|
||||
|
||||
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
|
||||
void OnSelectedMachineLost();
|
||||
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
|
||||
@@ -449,9 +449,7 @@ bool MachineObject::HasRecentLanMessage()
|
||||
|
||||
std::string MachineObject::get_access_code() const
|
||||
{
|
||||
if (get_user_access_code().empty())
|
||||
return access_code;
|
||||
return get_user_access_code();
|
||||
return access_code;
|
||||
}
|
||||
|
||||
void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
@@ -470,37 +468,6 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::erase_user_access_code()
|
||||
{
|
||||
this->user_access_code = "";
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
GUI::wxGetApp().app_config->erase("user_access_code", get_dev_id());
|
||||
//GUI::wxGetApp().app_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::set_user_access_code(std::string code, bool only_refresh)
|
||||
{
|
||||
this->user_access_code = code;
|
||||
if (only_refresh && !code.empty()) {
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config && !code.empty()) {
|
||||
GUI::wxGetApp().app_config->set_str("user_access_code", get_dev_id(), code);
|
||||
DeviceManager::update_local_machine(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string MachineObject::get_user_access_code() const
|
||||
{
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (config) {
|
||||
return GUI::wxGetApp().app_config->get("user_access_code", get_dev_id());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string MachineObject::get_show_printer_type() const
|
||||
{
|
||||
std::string printer_type = this->printer_type;
|
||||
@@ -2907,7 +2874,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
std::string access_code = j_pre["system"]["access_code"].get<std::string>();
|
||||
if (!access_code.empty()) {
|
||||
set_access_code(access_code);
|
||||
set_user_access_code(access_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4647,6 +4613,40 @@ void MachineObject::set_ctt_dlg( wxString text){
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::show_unsupported_dlg(int code)
|
||||
{
|
||||
// why: a dead control invites repeat clicks, and the frame is modeless - without the guard
|
||||
// every click stacks another one. Same shape as set_ctt_dlg above, including the reset on
|
||||
// both hide and close so a dismissed dialog can reappear on the next attempt.
|
||||
if (m_unsupported_dlg_shown) {
|
||||
return;
|
||||
}
|
||||
m_unsupported_dlg_shown = true;
|
||||
|
||||
// why: two codes so the user learns which kind of dead end this is - the slicer having no
|
||||
// translation for the command, or the printer's own config lacking the hardware to run it.
|
||||
const wxString text = (code == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) ?
|
||||
_L("This printer is not configured with the hardware this control needs.") :
|
||||
_L("This control is not supported on this printer.");
|
||||
|
||||
// note: constructed directly rather than through CallAfter because every publish_json caller
|
||||
// is on the UI thread - clicks come from wx handlers, and the agent marshals its own push
|
||||
// callbacks back to main before parse_json runs. set_ctt_dlg relies on the same property.
|
||||
auto unsupported_dlg = new GUI::SecondaryCheckDialog(nullptr, wxID_ANY, _L("Warning"),
|
||||
GUI::SecondaryCheckDialog::VisibleButtons::ONLY_CONFIRM);
|
||||
unsupported_dlg->update_text(text);
|
||||
unsupported_dlg->Bind(wxEVT_SHOW, [this](auto& e) {
|
||||
if (!e.IsShown()) {
|
||||
m_unsupported_dlg_shown = false;
|
||||
}
|
||||
});
|
||||
unsupported_dlg->Bind(wxEVT_CLOSE_WINDOW, [this](auto& e) {
|
||||
e.Skip();
|
||||
m_unsupported_dlg_shown = false;
|
||||
});
|
||||
unsupported_dlg->on_show();
|
||||
}
|
||||
|
||||
int MachineObject::publish_gcode(std::string gcode_str)
|
||||
{
|
||||
json j;
|
||||
|
||||
@@ -113,7 +113,6 @@ private:
|
||||
std::string dev_name;
|
||||
std::string dev_ip;
|
||||
std::string access_code;
|
||||
std::string user_access_code;
|
||||
|
||||
// type, time stamp, delay
|
||||
std::vector<std::tuple<std::string, uint64_t, uint64_t>> message_delay;
|
||||
@@ -228,11 +227,6 @@ public:
|
||||
std::string get_access_code() const;
|
||||
void set_access_code(std::string code, bool only_refresh = true);
|
||||
|
||||
/*user access code*/
|
||||
void set_user_access_code(std::string code, bool only_refresh = true);
|
||||
void erase_user_access_code();
|
||||
std::string get_user_access_code() const;
|
||||
|
||||
//PRINTER_TYPE printer_type = PRINTER_3DPrinter_UKNOWN;
|
||||
std::string printer_type; /* model_id */
|
||||
std::string get_show_printer_type() const;
|
||||
@@ -272,9 +266,11 @@ public:
|
||||
bool m_is_online;
|
||||
bool m_lan_mode_connection_state{false};
|
||||
bool m_set_ctt_dlg{ false };
|
||||
bool m_unsupported_dlg_shown{ false };
|
||||
void set_lan_mode_connection_state(bool state) {m_lan_mode_connection_state = state;};
|
||||
bool get_lan_mode_connection_state() {return m_lan_mode_connection_state;};
|
||||
void set_ctt_dlg( wxString text);
|
||||
void show_unsupported_dlg(int code);
|
||||
int parse_msg_count = 0;
|
||||
int keep_alive_count = 0;
|
||||
std::chrono::system_clock::time_point last_update_time; /* last received print data from machine */
|
||||
|
||||
@@ -637,7 +637,7 @@ ReselectMachineDialog::ReselectMachineDialog(wxWindow* parent)
|
||||
|
||||
wxBoxSizer* okGroupSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
UiAMSSlot* amsSlotOK= new UiAMSSlot(statusBar, colourOK, wxString("Ax\nPLA"), DataStatusType::OK, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(58), FromDIP(90)), 1.0, 1.0);
|
||||
wxStaticText* okLabel = new wxStaticText(statusBar, wxID_ANY, _CTX(L_CONTEXT("OK", "FilamentTrack"), "FilamentTrack"));
|
||||
wxStaticText* okLabel = new wxStaticText(statusBar, wxID_ANY, _L_CONTEXT(L_CONTEXT("OK", "FilamentTrack"), "FilamentTrack"));
|
||||
okLabel->SetFont(wxGetApp().normal_font());
|
||||
okLabel->SetForegroundColour(wxColour("#6B6B6B"));
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiAmsHumidityPopup.cpp
|
||||
/**********************************************************
|
||||
* File: uiAmsHumidityPopup.cpp
|
||||
* Description: The popup with DevAms Humidity
|
||||
*
|
||||
* \n class uiAmsHumidityPopup
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#include "uiAmsHumidityPopup.h"
|
||||
|
||||
@@ -191,4 +191,4 @@ void uiAmsPercentHumidityDryPopup::msw_rescale()
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiAmsHumidityPopup.h
|
||||
/**********************************************************
|
||||
* File: uiAmsHumidityPopup.h
|
||||
* Description: The popup with DevAms Humidity
|
||||
*
|
||||
* \n class uiAmsHumidityPopup
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "slic3r/GUI/Widgets/AMSItem.hpp"
|
||||
@@ -68,7 +68,7 @@ private:
|
||||
|
||||
wxStaticBitmap* m_dry_state_img;
|
||||
Label* m_dry_state;
|
||||
|
||||
|
||||
Label* m_humidity_header;
|
||||
Label* m_humidity_label;
|
||||
|
||||
@@ -81,4 +81,4 @@ private:
|
||||
wxSizer* m_sizer;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiDeviceUpdateVersion.cpp
|
||||
/**********************************************************
|
||||
* File: uiDeviceUpdateVersion.cpp
|
||||
* Description: The panel with firmware info
|
||||
*
|
||||
* \n class uiDeviceUpdateVersion
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#include "uiDeviceUpdateVersion.h"
|
||||
|
||||
@@ -114,4 +114,4 @@ void uiDeviceUpdateVersion::CreateWidgets()
|
||||
Layout();
|
||||
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiDeviceUpdateVersion.h
|
||||
/**********************************************************
|
||||
* File: uiDeviceUpdateVersion.h
|
||||
* Description: The panel with firmware info
|
||||
*
|
||||
* \n class uiDeviceUpdateVersion
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <wx/panel.h>
|
||||
@@ -44,4 +44,4 @@ private:
|
||||
wxStaticText* m_dev_version;
|
||||
wxStaticBitmap* m_dev_upgrade_indicator;
|
||||
};
|
||||
};// end of namespace Slic3r::GUI
|
||||
};// end of namespace Slic3r::GUI
|
||||
|
||||
@@ -112,9 +112,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
||||
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
|
||||
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
|
||||
|
||||
@@ -134,7 +134,7 @@ void Downloader::start_download(const std::string& full_url)
|
||||
Plater* plater = wxGetApp().plater();
|
||||
|
||||
mainframe->Freeze();
|
||||
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater->select_view_3D("3D");
|
||||
plater->select_view("plate");
|
||||
plater->get_current_canvas3D()->zoom_to_bed();
|
||||
|
||||
@@ -239,7 +239,7 @@ void ExtrusionCalibration::create()
|
||||
m_button_save_result->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
m_button_save_result->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_save, this);
|
||||
|
||||
m_button_last_step = new Button(m_step_2_panel, _CTX("Back", "Navigation")); // Back for english
|
||||
m_button_last_step = new Button(m_step_2_panel, _L_CONTEXT("Back", "Navigation")); // Back for english
|
||||
m_button_last_step->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
m_button_last_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_last, this);
|
||||
|
||||
@@ -261,7 +261,7 @@ void ExtrusionCalibration::create()
|
||||
top_sizer->Add(FromDIP(24), 0);
|
||||
top_sizer->Add(sizer_main, 1, wxEXPAND);
|
||||
top_sizer->Add(FromDIP(24), 0);
|
||||
SetSizer(top_sizer);
|
||||
SetSizerAndFit(top_sizer);
|
||||
|
||||
// set default nozzle
|
||||
m_comboBox_nozzle_dia->SetSelection(1);
|
||||
@@ -271,7 +271,6 @@ void ExtrusionCalibration::create()
|
||||
set_step(1);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
|
||||
input_value_finish();
|
||||
|
||||
+196
-130
@@ -35,6 +35,7 @@
|
||||
#include "Widgets/TextCtrl.h"
|
||||
|
||||
#include "../Utils/ColorSpaceConvert.hpp"
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
#ifdef __WXOSX__
|
||||
#define wxOSX true
|
||||
#else
|
||||
@@ -330,8 +331,10 @@ void Field::PostInitialize()
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (tab_id >= 0)
|
||||
wxGetApp().mainframe->select_tab(tab_id);
|
||||
if (tab_id >= 0) {
|
||||
static constexpr const char* kShortcutTabIds[] = {TAB_ID_HOME, TAB_ID_PREPARE, TAB_ID_PREVIEW, TAB_ID_MONITOR};
|
||||
wxGetApp().mainframe->select_tab(kShortcutTabIds[tab_id]);
|
||||
}
|
||||
if (tab_id > 0)
|
||||
// tab panel should be focused for correct navigation between tabs
|
||||
wxGetApp().tab_panel()->SetFocus();
|
||||
@@ -1043,14 +1046,14 @@ void TextCtrl::propagate_value()
|
||||
|
||||
void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) {
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
if (m_opt.nullable) {
|
||||
const bool m_is_na_val = value.empty() || (boost::any_cast<wxString>(value) == _(L("N/A")));
|
||||
if (!m_is_na_val)
|
||||
m_last_meaningful_value = value;
|
||||
text_ctrl()->SetValue(boost::any_cast<wxString>(value)); // BBS
|
||||
}
|
||||
else
|
||||
text_ctrl()->SetValue(value.empty() ? "" : boost::any_cast<wxString>(value)); // BBS // BBS: null value
|
||||
|
||||
text_ctrl()->SetValue(value.empty() ? wxString() : boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
|
||||
if (!change_event) {
|
||||
@@ -1187,18 +1190,24 @@ void CheckBox::set_value(const boost::any& value, bool change_event)
|
||||
m_disable_change_event = !change_event;
|
||||
if (m_opt.nullable) {
|
||||
const bool is_value_unsigned_char = value.type() == typeid(unsigned char);
|
||||
bool bool_value = false;
|
||||
|
||||
m_is_na_val = value.empty() || (is_value_unsigned_char &&
|
||||
boost::any_cast<unsigned char>(value) == ConfigOptionBoolsNullable::nil_value());
|
||||
if (!m_is_na_val)
|
||||
m_last_meaningful_value = is_value_unsigned_char ? value : static_cast<unsigned char>(boost::any_cast<bool>(value));
|
||||
|
||||
const auto bool_value = is_value_unsigned_char ?
|
||||
boost::any_cast<unsigned char>(value) != 0 :
|
||||
boost::any_cast<bool>(value);
|
||||
dynamic_cast<::CheckBox*>(window)->SetValue(m_is_na_val ? false : bool_value); // BBS
|
||||
if (!m_is_na_val) {
|
||||
bool_value = is_value_unsigned_char ?
|
||||
boost::any_cast<unsigned char>(value) != 0 :
|
||||
boost::any_cast<bool>(value);
|
||||
m_last_meaningful_value = is_value_unsigned_char ? value : static_cast<unsigned char>(bool_value);
|
||||
}
|
||||
|
||||
dynamic_cast<::CheckBox*>(window)->SetValue(bool_value);
|
||||
}
|
||||
else if (!value.empty()) // BBS: null value
|
||||
else if (!value.empty()){ // BBS: null value
|
||||
dynamic_cast<::CheckBox*>(window)->SetValue(boost::any_cast<bool>(value)); // BBS
|
||||
}
|
||||
|
||||
dynamic_cast<::CheckBox*>(window)->SetHalfChecked(value.empty());
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
@@ -1328,27 +1337,7 @@ void SpinCtrl::BUILD() {
|
||||
if (!parsed || value < INT_MIN || value > INT_MAX)
|
||||
tmp_value = UNDEF_VALUE;
|
||||
else {
|
||||
tmp_value = std::min(std::max((int)value, temp->GetMin()), temp->GetMax());
|
||||
#ifdef __WXOSX__
|
||||
#ifdef UNDEFINED__WXOSX__ // BBS
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
SpinInput* spin = static_cast<SpinInput*>(window);
|
||||
spin->SetValue(tmp_value);
|
||||
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
|
||||
// discard this selection and set insertion point to the end of string
|
||||
// temp->GetText()->SetInsertionPointEnd();
|
||||
#endif
|
||||
#else
|
||||
// update value for the control only if it was changed in respect to the Min/max values
|
||||
if (tmp_value != (int)value) {
|
||||
temp->SetValue(tmp_value);
|
||||
// But after SetValue() cursor ison the first position
|
||||
// so put it to the end of string
|
||||
// int pos = std::to_string(tmp_value).length();
|
||||
// temp->SetSelection(pos, pos);
|
||||
}
|
||||
#endif
|
||||
tmp_value = (int)value;
|
||||
}
|
||||
}), temp->GetTextCtrl()->GetId());
|
||||
|
||||
@@ -1369,6 +1358,10 @@ void SpinCtrl::propagate_value()
|
||||
on_kill_focus();
|
||||
} else {
|
||||
auto ctrl = dynamic_cast<SpinInput *>(window);
|
||||
tmp_value = std::min(std::max(tmp_value, ctrl->GetMin()), ctrl->GetMax());
|
||||
if (ctrl->GetValue() != tmp_value)
|
||||
ctrl->SetValue(tmp_value); // Clamp now when the user is done typing (kill focus / Enter / spin arrows)
|
||||
|
||||
if (m_value.empty()
|
||||
? !ctrl->GetTextCtrl()->GetLabel().IsEmpty()
|
||||
: ctrl->GetValue() != boost::any_cast<int>(m_value))
|
||||
@@ -1413,39 +1406,6 @@ using choice_ctrl = ::ComboBox; // BBS
|
||||
|
||||
static std::map<std::string, DynamicList*> dynamic_lists;
|
||||
|
||||
static bool is_plugin_printer_agent_key(const std::string& value)
|
||||
{
|
||||
return value.rfind("plugin:", 0) == 0;
|
||||
}
|
||||
|
||||
static int printer_agent_item_for_enum_index(const choice_ctrl* field, int enum_index)
|
||||
{
|
||||
if (!field)
|
||||
return -1;
|
||||
|
||||
const unsigned int count = field->GetCount();
|
||||
for (unsigned int idx = 0; idx < count; ++idx) {
|
||||
if (void* data = field->GetClientData(idx)) {
|
||||
const int stored = static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
|
||||
if (stored == enum_index)
|
||||
return static_cast<int>(idx);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int printer_agent_enum_index_for_item(const choice_ctrl* field, int item_index, int fallback)
|
||||
{
|
||||
if (!field || item_index < 0)
|
||||
return fallback;
|
||||
|
||||
if (void* data = field->GetClientData(item_index))
|
||||
return static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void Choice::register_dynamic_list(std::string const &optname, DynamicList *list) { dynamic_lists.emplace(optname, list); }
|
||||
|
||||
void DynamicList::update()
|
||||
@@ -1528,33 +1488,7 @@ void Choice::BUILD()
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
if (! m_opt.enum_labels.empty() || ! m_opt.enum_values.empty()) {
|
||||
if (m_opt_id == "printer_agent") {
|
||||
const bool has_builtin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
|
||||
[](const std::string& value) { return !is_plugin_printer_agent_key(value); });
|
||||
const bool has_plugin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
|
||||
[](const std::string& value) { return is_plugin_printer_agent_key(value); });
|
||||
|
||||
auto append_agent_rows = [this, temp](bool plugins) {
|
||||
for (size_t i = 0; i < m_opt.enum_values.size(); ++i) {
|
||||
const bool is_plugin = is_plugin_printer_agent_key(m_opt.enum_values[i]);
|
||||
if (is_plugin != plugins)
|
||||
continue;
|
||||
|
||||
const wxString label = i < m_opt.enum_labels.size() ? _(m_opt.enum_labels[i]) : wxString(m_opt.enum_values[i]);
|
||||
const int item = temp->Append(label);
|
||||
temp->SetClientData(item, reinterpret_cast<void*>(static_cast<uintptr_t>(i + 1)));
|
||||
}
|
||||
};
|
||||
|
||||
if (has_builtin_agents) {
|
||||
temp->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(false);
|
||||
}
|
||||
if (has_plugin_agents) {
|
||||
temp->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(true);
|
||||
}
|
||||
} else if (m_opt.enum_labels.empty()) {
|
||||
if (m_opt.enum_labels.empty()) {
|
||||
// Append non-localized enum_values
|
||||
for (auto el : m_opt.enum_values)
|
||||
temp->Append(el);
|
||||
@@ -1661,7 +1595,7 @@ void Choice::set_selection()
|
||||
switch (m_opt.type) {
|
||||
case coEnum:{
|
||||
const int val = m_opt.default_value->getInt();
|
||||
field->SetSelection(m_opt_id == "printer_agent" ? printer_agent_item_for_enum_index(field, val) : val);
|
||||
field->SetSelection(val);
|
||||
break;
|
||||
}
|
||||
case coFloat:
|
||||
@@ -1711,12 +1645,7 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda
|
||||
}
|
||||
|
||||
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
|
||||
if (m_opt_id == "printer_agent") {
|
||||
const int enum_index = idx == m_opt.enum_values.size() ?
|
||||
(m_opt.default_value ? m_opt.default_value->getInt() : 0) :
|
||||
static_cast<int>(idx);
|
||||
field->SetSelection(printer_agent_item_for_enum_index(field, enum_index));
|
||||
} else if (idx == m_opt.enum_values.size())
|
||||
if (idx == m_opt.enum_values.size())
|
||||
field->SetValue(value);
|
||||
else
|
||||
field->SetSelection(idx);
|
||||
@@ -1782,33 +1711,11 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
||||
case coEnum:
|
||||
// BBS
|
||||
case coEnums: {
|
||||
auto printer_agent_index_from_key = [this](const std::string& key) {
|
||||
auto it = std::find(m_opt.enum_values.begin(), m_opt.enum_values.end(), key);
|
||||
if (it != m_opt.enum_values.end())
|
||||
return static_cast<int>(it - m_opt.enum_values.begin());
|
||||
return m_opt.default_value ? m_opt.default_value->getInt() : 0;
|
||||
};
|
||||
|
||||
int val = 0;
|
||||
if (m_opt_id == "printer_agent") {
|
||||
if (const int* int_value = boost::any_cast<int>(&value))
|
||||
val = *int_value;
|
||||
else if (const wxString* wx_value = boost::any_cast<wxString>(&value))
|
||||
val = printer_agent_index_from_key(into_u8(*wx_value));
|
||||
else if (const std::string* string_value = boost::any_cast<std::string>(&value))
|
||||
val = printer_agent_index_from_key(*string_value);
|
||||
else {
|
||||
m_disable_change_event = false;
|
||||
return;
|
||||
}
|
||||
} else
|
||||
val = boost::any_cast<int>(value);
|
||||
int val = boost::any_cast<int>(value);
|
||||
|
||||
int selection = val;
|
||||
|
||||
if (m_opt_id == "printer_agent") {
|
||||
selection = printer_agent_item_for_enum_index(field, val);
|
||||
} else if (m_opt_id == "input_shaping_type") {
|
||||
if (m_opt_id == "input_shaping_type") {
|
||||
if (field != nullptr) {
|
||||
const unsigned int count = field->GetCount();
|
||||
int match_index = -1;
|
||||
@@ -1930,12 +1837,6 @@ boost::any& Choice::get_value()
|
||||
{
|
||||
if (m_opt.nullable && field->GetSelection() == -1)
|
||||
m_value = ConfigOptionEnumsGenericNullable::nil_value();
|
||||
else if (m_opt_id == "printer_agent")
|
||||
{
|
||||
const int selection = field->GetSelection();
|
||||
const int fallback = m_opt.default_value ? m_opt.default_value->getInt() : 0;
|
||||
m_value = printer_agent_enum_index_for_item(field, selection, fallback);
|
||||
}
|
||||
else if (m_opt_id == "input_shaping_type")
|
||||
{
|
||||
int selection = field->GetSelection();
|
||||
@@ -2077,6 +1978,171 @@ void Choice::msw_rescale()
|
||||
}
|
||||
|
||||
|
||||
// PrinterAgentChoice
|
||||
|
||||
void PrinterAgentChoice::reload_rows()
|
||||
{
|
||||
auto* combo = dynamic_cast<choice_ctrl*>(window); // wxWidgets ComboBox
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
// clear ComboBox
|
||||
combo->Clear();
|
||||
|
||||
// helpers
|
||||
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
const bool has_builtin_agents = std::any_of(agents.begin(), agents.end(),
|
||||
[](const PrinterAgentInfo& a) { return !a.is_plugin(); });
|
||||
const bool has_plugin_agents = std::any_of(agents.begin(), agents.end(),
|
||||
[](const PrinterAgentInfo& a) { return a.is_plugin(); });
|
||||
|
||||
auto append_agent_rows = [combo](bool is_plugin)
|
||||
{
|
||||
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
for (size_t i = 0; i < agents.size(); ++i)
|
||||
{
|
||||
if (agents[i].is_plugin() != is_plugin)
|
||||
continue;
|
||||
const int item = combo->Append(_(agents[i].display_name));
|
||||
// why: carry the agent-id string on the row. alias is an owned wxString (auto-freed, never rendered)
|
||||
combo->SetItemAlias(item, from_u8(agents[i].id));
|
||||
}
|
||||
};
|
||||
|
||||
// append rows
|
||||
if (has_builtin_agents)
|
||||
{
|
||||
combo->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(false); // append rows for agents that are not plugins
|
||||
}
|
||||
if (has_plugin_agents)
|
||||
{
|
||||
combo->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(true); // append rows for agents that are plugins
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterAgentChoice::BUILD()
|
||||
{
|
||||
wxSize size(def_width_wider() * m_em_unit, wxDefaultCoord);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
|
||||
|
||||
static Builder<choice_ctrl> builder;
|
||||
choice_ctrl* temp = builder.build(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr,
|
||||
wxCB_READONLY);
|
||||
temp->Clear();
|
||||
temp->GetDropDown().SetUseContentWidth(true);
|
||||
if (parent_is_custom_ctrl && m_opt.height < 0)
|
||||
opt_height = (double)temp->GetTextCtrl()->GetSize().GetHeight() / m_em_unit;
|
||||
temp->SetTextLabel(_L(m_opt.sidetext));
|
||||
m_combine_side_text = true;
|
||||
#ifdef __WXGTK3__
|
||||
wxSize best_sz = temp->GetBestSize();
|
||||
if (best_sz.x > size.x) temp->SetSize(best_sz);
|
||||
#endif
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
reload_rows();
|
||||
|
||||
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
|
||||
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
|
||||
}
|
||||
|
||||
// Resolve CONFIG id string to a matching row in the live REGISTRY. "" uses the vendor default.
|
||||
// An unregistered id clears selection and shows "<id> (missing)" as free text.
|
||||
void PrinterAgentChoice::set_value(const std::string& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
|
||||
// check if any row's corresponding id matches the agent id we are attempting to set
|
||||
const std::string effective_agent_id = wxGetApp().resolve_printer_agent_id(value);
|
||||
const unsigned int count = field->GetCount();
|
||||
int match = wxNOT_FOUND;
|
||||
for (unsigned int i = 0; i < count; ++i)
|
||||
{
|
||||
if (into_u8(field->GetItemAlias(i)) == effective_agent_id) // if alias == id
|
||||
{
|
||||
match = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// based on match or not, set selection and value
|
||||
// - SetSelection and SetValue are UI to manipulate the display of the ComboBox
|
||||
// - SetSelection automatically calls SetValue for the same value
|
||||
// - we can also SetValue separately from SetSelection
|
||||
if (match == wxNOT_FOUND)
|
||||
{
|
||||
field->SetSelection(wxNOT_FOUND); // nothing shows as selected in the dropdown
|
||||
field->SetValue(from_u8(value + " (missing)")); // set a value not in the selection (upper display field)
|
||||
}
|
||||
else
|
||||
{
|
||||
// display name of agent shows both in upper display field and appears selected in dropdown
|
||||
field->SetSelection(match);
|
||||
}
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
// Accept boost::any values from callers (usually to OptionsGroup/Field parent classes) and normalize them to an agent id.
|
||||
// Then use PrinterAgentChoice::set_value(std::string& value, ...)
|
||||
void PrinterAgentChoice::set_value(const boost::any& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
if (value.empty())
|
||||
{
|
||||
field->SetValue("");
|
||||
m_value = value;
|
||||
m_disable_change_event = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
if (const std::string* s = boost::any_cast<std::string>(&value))
|
||||
id = *s;
|
||||
else if (const wxString* w = boost::any_cast<wxString>(&value))
|
||||
id = into_u8(*w);
|
||||
set_value(id, change_event);
|
||||
}
|
||||
|
||||
// A real row returns its alias, which is the agent id. Header rows, missing rows,
|
||||
// and no selection return empty boost::any so the custom writer leaves config unchanged.
|
||||
boost::any& PrinterAgentChoice::get_value()
|
||||
{
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
const int sel = field->GetSelection();
|
||||
const std::string id = sel < 0 ? std::string{} : into_u8(field->GetItemAlias(sel));
|
||||
if (id.empty())
|
||||
m_value = boost::any{};
|
||||
else
|
||||
m_value = id;
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void PrinterAgentChoice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); }
|
||||
void PrinterAgentChoice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }
|
||||
|
||||
void PrinterAgentChoice::msw_rescale()
|
||||
{
|
||||
Field::msw_rescale();
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window)->GetTextCtrl();
|
||||
wxSize size(wxDefaultSize);
|
||||
size.SetWidth((m_opt.width > 0 ? m_opt.width : def_width_wider()) * m_em_unit);
|
||||
field->SetMinSize(wxSize(-1, int(1.5f * field->GetFont().GetPixelSize().y + 0.5f)));
|
||||
field->SetSize(size);
|
||||
|
||||
dynamic_cast<choice_ctrl*>(window)->Rescale();
|
||||
}
|
||||
|
||||
void PluginField::BUILD()
|
||||
{
|
||||
auto* panel = new wxPanel(m_parent, wxID_ANY);
|
||||
|
||||
@@ -469,6 +469,44 @@ public:
|
||||
void suppress_scroll();
|
||||
};
|
||||
|
||||
// printer_agent is a coString whose choices come from the live agent registry.
|
||||
// PrinterAgentChoice uses a ComboBox directly because Choice expects static config enums.
|
||||
// Real rows carry the stored agent id in the row alias (SetItemAlias/GetItemAlias).
|
||||
class PrinterAgentChoice : public Field
|
||||
{
|
||||
using Field::Field;
|
||||
|
||||
public:
|
||||
PrinterAgentChoice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id)
|
||||
{
|
||||
}
|
||||
|
||||
PrinterAgentChoice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(
|
||||
parent, opt, id)
|
||||
{
|
||||
}
|
||||
|
||||
~PrinterAgentChoice()
|
||||
{
|
||||
}
|
||||
|
||||
wxWindow* window{nullptr};
|
||||
|
||||
void BUILD() override;
|
||||
// Clear and repopulate rows from the live registry (grouped System agents / Plugins).
|
||||
// Does not change selection; the caller follows with set_value(stored id).
|
||||
void reload_rows();
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
void msw_rescale() override;
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class PluginField : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
|
||||
@@ -105,7 +105,7 @@ void FilamentMapManualPanel::OnTimer(wxTimerEvent &)
|
||||
m_suggestion_panel->Hide();
|
||||
} else {
|
||||
m_errors->SetLabel(wxString::Format(_L("Error: %s extruder has no available %s nozzle, current group result is invalid."),
|
||||
invalid_eid == 0 ? _L("Left") : _L("Right"),
|
||||
invalid_eid == 0 ? _L_CONTEXT("Left", "Nozzle position") : _L_CONTEXT("Right", "Nozzle position"),
|
||||
invalid_nozzle == NozzleVolumeType::nvtStandard ? _L("Standard") : _L("High Flow")));
|
||||
// Re-wrap: wrapping only applies to the label text present when Wrap is called,
|
||||
// and the label was empty at construction time.
|
||||
|
||||
@@ -105,9 +105,8 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
|
||||
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
|
||||
container_sizer->Add(dlg_btns, 0, wxEXPAND);
|
||||
|
||||
SetSizer(container_sizer);
|
||||
SetSizerAndFit(container_sizer);
|
||||
Layout();
|
||||
container_sizer->Fit(this);
|
||||
|
||||
// Position the dialog relative to the parent window
|
||||
if (GetParent()) {
|
||||
|
||||
@@ -1134,8 +1134,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
if (current_top_layer_only != required_top_layer_only)
|
||||
m_viewer.toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
|
||||
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
|
||||
|
||||
// avoid processing if called with the same gcode_result
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
|
||||
@@ -4268,7 +4269,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
};
|
||||
|
||||
auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
|
||||
imgui.text(_CTX_utf8("Print", "Noun"));
|
||||
imgui.text(_u8L_CONTEXT("Print", "Noun"));
|
||||
ImGui::SameLine();
|
||||
|
||||
float icon_size = ImGui::GetTextLineHeight();
|
||||
@@ -4302,7 +4303,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
for (const PartialTime& item : partial_times) {
|
||||
switch (item.type)
|
||||
{
|
||||
case PartialTime::EType::Print: { labels.push_back(_CTX_utf8("Print", "Noun")); break; }
|
||||
case PartialTime::EType::Print: { labels.push_back(_u8L_CONTEXT("Print", "Noun")); break; }
|
||||
case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
|
||||
case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
|
||||
}
|
||||
|
||||
@@ -333,9 +333,12 @@ public:
|
||||
|
||||
libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); }
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing the preview (ported from preFlight)
|
||||
// ORCA: darken the layers not scrubbed to while using the preview layer slider
|
||||
void set_dim_previous_layers(bool value) { m_viewer.set_dim_previous_layers(value); }
|
||||
bool is_dim_previous_layers() const { return m_viewer.is_dim_previous_layers(); }
|
||||
// ORCA: brightness of those darkened layers, 1.0 = unchanged, 0.0 = black
|
||||
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
|
||||
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
|
||||
+134
-77
@@ -282,7 +282,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D&
|
||||
ImGui::SetCursorPosX(input_align);
|
||||
ImGui::BBLDragFloat("##adaptive_input", &m_adaptive_quality, 0.05f, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
if (imgui.button(_L("Smooth")))
|
||||
if (imgui.button(_L_CONTEXT("Smooth", "Mesh action")))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
|
||||
ImGui::SameLine();
|
||||
text_align = std::max(text_align, ImGui::GetCursorPosX());
|
||||
@@ -1842,6 +1842,10 @@ void GLCanvas3D::enable_separator_toolbar(bool enable)
|
||||
m_separator_toolbar.set_enabled(enable);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::has_mouse_capture() const {
|
||||
return m_canvas != nullptr && m_canvas->HasCapture();
|
||||
}
|
||||
|
||||
void GLCanvas3D::zoom_to_bed()
|
||||
{
|
||||
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
|
||||
@@ -2182,7 +2186,7 @@ void GLCanvas3D::render(bool only_init)
|
||||
|
||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||
// In that case the tooltip should be hidden.
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0. || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
|
||||
if (tooltip.empty())
|
||||
tooltip = m_layers_editing.get_tooltip(*this);
|
||||
|
||||
@@ -2867,6 +2871,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
}
|
||||
|
||||
if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
|
||||
// The tower size estimate reads printer- and filament-scope keys, which the print preset
|
||||
// does not carry; built once here rather than per plate.
|
||||
const DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
|
||||
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
||||
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
||||
PartPlate* part_plate = ppl.get_plate(plate_id);
|
||||
@@ -2880,7 +2887,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
||||
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
||||
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
||||
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
|
||||
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||
// BBS
|
||||
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
||||
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
||||
@@ -2891,51 +2898,26 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
if (part_plate->get_objects_on_this_plate().empty()) continue;
|
||||
|
||||
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
|
||||
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
|
||||
|
||||
{
|
||||
const float margin = WIPE_TOWER_MARGIN + brim_width;
|
||||
BoundingBoxf3 plate_bbox = part_plate->get_bounding_box();
|
||||
BoundingBoxf plate_bbox_2d(Vec2d(plate_bbox.min(0), plate_bbox.min(1)), Vec2d(plate_bbox.max(0), plate_bbox.max(1)));
|
||||
const std::vector<Pointfs> &extruder_areas = part_plate->get_extruder_areas();
|
||||
for (Pointfs points : extruder_areas) {
|
||||
BoundingBoxf bboxf(points);
|
||||
plate_bbox_2d.min = plate_bbox_2d.min(0) >= bboxf.min(0) ? plate_bbox_2d.min : bboxf.min;
|
||||
plate_bbox_2d.max = plate_bbox_2d.max(0) <= bboxf.max(0) ? plate_bbox_2d.max : bboxf.max;
|
||||
}
|
||||
|
||||
coordf_t plate_bbox_x_min_local_coord = plate_bbox_2d.min(0) - plate_origin(0);
|
||||
coordf_t plate_bbox_x_max_local_coord = plate_bbox_2d.max(0) - plate_origin(0);
|
||||
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
|
||||
|
||||
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
|
||||
// update for wipe tower position
|
||||
{
|
||||
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
|
||||
a,
|
||||
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
|
||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||
}
|
||||
} else {
|
||||
const float margin = 2.f;
|
||||
auto tower_bottom = current_print->wipe_tower_data().wipe_tower_mesh_data->bottom;
|
||||
tower_bottom.translate(scaled(Vec2d{x, y}));
|
||||
tower_bottom.translate(scaled(Vec2d{plate_origin[0], plate_origin[1]}));
|
||||
auto tower_bottom_bbox = get_extents(tower_bottom);
|
||||
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_id)->get_build_volume(true);
|
||||
BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1])));
|
||||
Vec2f offset = WipeTower::move_box_inside_box(tower_bottom_bbox, plate_bbox2d, scaled(margin));
|
||||
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
|
||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
|
||||
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
|
||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||
}
|
||||
// The stored position is already clamped onto the bed, by
|
||||
// set_default_wipe_tower_pos_for_plate and again on every drag.
|
||||
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
|
||||
// update for wipe tower position
|
||||
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
|
||||
a,
|
||||
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
|
||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||
} else {
|
||||
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
|
||||
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
|
||||
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
|
||||
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
||||
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4170,6 +4152,23 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// BBS: single snapshot
|
||||
Plater::SingleSnapshot single(wxGetApp().plater());
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// On macOS, the mouse key state is only present for mouse btn related events such as wxEVT_LEFT_DOWN.
|
||||
// For other events, all buttons are reported as non-pressed, such as window leaving event. This causes
|
||||
// imgui stopped responding if cursor moved out of window, such as
|
||||
// https://github.com/OrcaSlicer/OrcaSlicer/pull/14999#issuecomment-5151344759
|
||||
// We solve this by correcting the state of the event from the actual mouse state querying with `wxGetMouseState()`
|
||||
// so it works like on other platforms.
|
||||
{
|
||||
const auto state = wxGetMouseState();
|
||||
evt.SetLeftDown(state.LeftIsDown());
|
||||
evt.SetMiddleDown(state.MiddleIsDown());
|
||||
evt.SetRightDown(state.RightIsDown());
|
||||
evt.SetAux1Down(state.Aux1IsDown());
|
||||
evt.SetAux2Down(state.Aux2IsDown());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
const float scale = m_retina_helper->get_scale_factor();
|
||||
evt.SetX(evt.GetX() * scale);
|
||||
@@ -4183,11 +4182,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// ignore left up events coming from imgui windows and not processed by them
|
||||
m_mouse.ignore_left_up = true;
|
||||
m_tooltip.set_in_imgui(false);
|
||||
if (imgui->update_mouse_data(evt)) {
|
||||
|
||||
// while a non-ImGui drag is already in progress (gizmo grabber, object move, rectangle selection, layer editing),
|
||||
// don't let ImGui/ImGuizmo claim the event just because the cursor is hovering something like the navigator cube
|
||||
// that incorrectly suppresses the active drag's tooltip and can interrupt its processing. The active drag always takes priority.
|
||||
const bool other_drag_active = m_gizmos.is_dragging() || m_mouse.dragging || m_rectangle_selection.is_dragging() || m_layers_editing.state == LayersEditing::Editing;
|
||||
|
||||
if (imgui->update_mouse_data(evt) && !other_drag_active) {
|
||||
if ((evt.LeftDown() || (evt.Moving() && (evt.AltDown() || evt.ShiftDown()))) && m_canvas != nullptr)
|
||||
m_canvas->SetFocus();
|
||||
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
|
||||
m_tooltip.set_in_imgui(true);
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
const bool imgui_dragging_active = (GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0) || m_navigator_dragging;
|
||||
if (!has_mouse_capture() && imgui_dragging_active)
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// release capture as soon as the button goes up
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
render();
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
@@ -4284,6 +4299,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_main_toolbar.on_mouse(evt2, *this);
|
||||
}
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (!has_mouse_capture() && evt.LeftIsDown() && m_gizmos.is_dragging())
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
@@ -4389,6 +4408,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// Start editing the layer height.
|
||||
m_layers_editing.state = LayersEditing::Editing;
|
||||
_perform_layer_editing_action(&evt);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -4402,6 +4424,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
|
||||
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -4469,6 +4495,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = m_mouse.scene_position;
|
||||
m_sequential_print_clearance_first_displacement = true;
|
||||
m_moving = true;
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4477,6 +4506,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_mouse.drag.move_volume_idx != -1 && m_layers_editing.state == LayersEditing::Unknown) {
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (!m_mouse.drag.move_requires_threshold) {
|
||||
m_mouse.dragging = true;
|
||||
Vec3d cur_pos = m_mouse.drag.start_position_3D;
|
||||
@@ -4528,6 +4561,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
||||
//BBS not in assemble view
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_rectangle_selection.dragging(pos.cast<double>());
|
||||
m_dirty = true;
|
||||
}
|
||||
@@ -4537,12 +4574,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
|
||||
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
|
||||
if (m_layers_editing.state == LayersEditing::Editing) {
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
_perform_layer_editing_action(&evt);
|
||||
m_mouse.position = pos.cast<double>();
|
||||
}
|
||||
}
|
||||
// do not process the dragging if the left mouse was set down in another canvas
|
||||
else if (is_camera_rotate(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// Orca: Sphere rotation for painting view
|
||||
// if dragging over blank area with left button or other button mapped to rotate, then rotate
|
||||
bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
|
||||
@@ -4622,6 +4666,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
|
||||
}
|
||||
else if (is_camera_pan(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
|
||||
if (m_mouse.is_start_position_2D_defined()) {
|
||||
// get point in model space at Z = 0
|
||||
@@ -4686,7 +4734,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);
|
||||
@@ -6033,9 +6083,18 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
{
|
||||
if (!wxGetApp().show_3d_navigator()) {
|
||||
m_canvas_toolbar_pos[0] = 0;
|
||||
m_navigator_dragging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix stealing capture event from other drag events
|
||||
const bool other_drag_active = !m_navigator_dragging && (m_moving || m_rectangle_selection.is_dragging() || m_gizmos.is_dragging() || m_layers_editing.state == LayersEditing::Editing);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const bool saved_mouse_down0 = io.MouseDown[0];
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = false;
|
||||
|
||||
ImGuizmo::BeginFrame();
|
||||
|
||||
auto& style = ImGuizmo::GetStyle();
|
||||
@@ -6047,12 +6106,12 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "Y"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _CTX_utf8("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _CTX_utf8("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _CTX_utf8("Right", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _u8L_CONTEXT("Front", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _u8L_CONTEXT("Top", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _u8L_CONTEXT("Bottom", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
||||
|
||||
float sc = get_scale();
|
||||
#ifdef WIN32
|
||||
@@ -6060,7 +6119,6 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
sc *= (float) dpi / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const float viewManipulateLeft = 0;
|
||||
const float viewManipulateTop = io.DisplaySize.y;
|
||||
const float camDistance = 8.f;
|
||||
@@ -6084,6 +6142,10 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
|
||||
0x00101010);
|
||||
|
||||
// Restore the real mouse-down state
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = saved_mouse_down0;
|
||||
|
||||
if (result.changed) {
|
||||
for (unsigned int c = 0; c < 4; ++c) {
|
||||
for (unsigned int r = 0; r < 4; ++r) {
|
||||
@@ -6115,6 +6177,8 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
|
||||
request_extra_frame();
|
||||
}
|
||||
|
||||
m_navigator_dragging = result.dragging;
|
||||
}
|
||||
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||
@@ -6913,7 +6977,7 @@ void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) {
|
||||
else
|
||||
m_sel_plate_toolbar.show_stats_item = false;
|
||||
|
||||
if (force_selected && m_sel_plate_toolbar.show_stats_item)
|
||||
if (force_selected && m_sel_plate_toolbar.show_stats_item && m_sel_plate_toolbar.m_all_plates_stats_item)
|
||||
m_sel_plate_toolbar.m_all_plates_stats_item->selected = true;
|
||||
}
|
||||
|
||||
@@ -9132,7 +9196,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
||||
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
|
||||
view3d_canvas->reload_scene(true);
|
||||
}
|
||||
app.mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
app.mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -9226,8 +9290,12 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
||||
|
||||
//ORCA ImGui::IsWindowHovered() returns false when left_down events on buttons that causes scrollbar disappears for a short time
|
||||
auto win_pos = ImGui::GetWindowPos();
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll); // use non clipped rectangle to reserve clickable area for scrollbar track
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered;
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll);
|
||||
|
||||
// Also show scrollbar visible and continue to capture mouse position
|
||||
const bool is_scrollbar_active_drag = GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow();
|
||||
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered || is_scrollbar_active_drag;
|
||||
|
||||
imgui.end();
|
||||
}
|
||||
@@ -10534,9 +10602,8 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
|
||||
wxString region = L"en";
|
||||
if (language.find("zh") == 0)
|
||||
region = L"zh";
|
||||
// Use the generic dual-nozzle PLA+PETG guide rather than the H2D-specific page
|
||||
// so the link is relevant for all dual-extrusion printers, not just Bambu H2D. (#12073)
|
||||
wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/pla-and-petg-dual-extrusion", region));
|
||||
// Although this link looks like it's only for the H2D, its guidance is generic.
|
||||
wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/h2d-pla-and-petg-mutual-support", region));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -10670,24 +10737,14 @@ bool GLCanvas3D::is_flushing_matrix_error() {
|
||||
if (!Sidebar::should_show_SEMM_buttons())
|
||||
return false;
|
||||
|
||||
std::vector<int> plate_extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_extruders(true);
|
||||
if (plate_extruders.size() < 2)
|
||||
return false;
|
||||
|
||||
const auto &project_config = wxGetApp().preset_bundle->project_config;
|
||||
const std::vector<double> &config_matrix = (project_config.option<ConfigOptionFloats>("flush_volumes_matrix"))->values;
|
||||
const std::vector<double> &config_multiplier = (project_config.option<ConfigOptionFloats>("flush_multiplier"))->values;
|
||||
|
||||
for (auto multiplier : config_multiplier) {
|
||||
if (multiplier == 0) return true;
|
||||
}
|
||||
|
||||
int matrix_len = config_matrix.size() / config_multiplier.size();
|
||||
int row_len = std::sqrt(matrix_len);
|
||||
for (int i = 0; i < config_matrix.size(); i++)
|
||||
{
|
||||
int relative_id = i % matrix_len;
|
||||
int row_id = relative_id / row_len;
|
||||
int col_id = relative_id % row_len;
|
||||
if (row_id != col_id && config_matrix[i] == 0) return true;
|
||||
}
|
||||
return false;
|
||||
return has_zero_flush_volume_for_used_filaments(config_matrix, config_multiplier, plate_extruders);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_any_volume_outside() const
|
||||
|
||||
@@ -589,6 +589,7 @@ private:
|
||||
bool m_toolpath_outside{ false };
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
|
||||
//BBS:add plate related logic
|
||||
mutable std::vector<int> m_hover_volume_idxs;
|
||||
@@ -916,6 +917,7 @@ public:
|
||||
void update_volumes_colors_by_extruder();
|
||||
|
||||
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
|
||||
bool has_mouse_capture() const;
|
||||
|
||||
void render(bool only_init = false);
|
||||
bool is_rendering_enabled()
|
||||
@@ -1117,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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
+180
-59
@@ -96,6 +96,7 @@
|
||||
#include "../Utils/PresetUpdater.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/Process.hpp"
|
||||
#include "../Utils/wxInspectorPlugins/Registration.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "../Utils/Http.hpp"
|
||||
#include "../Utils/InstanceID.hpp"
|
||||
@@ -306,6 +307,20 @@ public:
|
||||
#endif // !__APPLE__
|
||||
)
|
||||
{
|
||||
// Some desktop environments ignore splash screen typed window properties
|
||||
// when running the app through Wayland,resulting in the titlebar being shown
|
||||
// on the splash screen. The code below creates a client-side window decoration
|
||||
// when running on Wayland and then removes that decoration. This ensures every
|
||||
// environment correctly targets and removes the titlebar for this screen.
|
||||
#if defined(__WXGTK__)
|
||||
if (Slic3r::GUI::is_running_on_wayland()) {
|
||||
GtkWidget *empty = gtk_fixed_new();
|
||||
gtk_widget_set_size_request(empty, 0, 0);
|
||||
gtk_window_set_titlebar(GTK_WINDOW(GetHandle()), empty);
|
||||
gtk_window_set_decorated(GTK_WINDOW(GetHandle()), false);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->SetPosition(pos);
|
||||
this->CenterOnScreen();
|
||||
|
||||
@@ -798,12 +813,12 @@ void GUI_App::post_init()
|
||||
m_open_method = "url";
|
||||
} else {
|
||||
if (this->init_params->input_gcode) {
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
|
||||
m_open_method = "gcode";
|
||||
} else {
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
wxArrayString input_files;
|
||||
for (auto& file : this->init_params->input_files) {
|
||||
@@ -837,7 +852,7 @@ void GUI_App::post_init()
|
||||
mainframe->Freeze();
|
||||
#endif
|
||||
plater_->canvas3D()->enable_render(false);
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
plater_->select_view_3D("3D");
|
||||
//BBS init the opengl resource here
|
||||
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
||||
@@ -875,9 +890,9 @@ void GUI_App::post_init()
|
||||
}
|
||||
}
|
||||
if (is_editor())
|
||||
mainframe->select_tab(size_t(0));
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
if (app_config->get("default_page") == "1")
|
||||
mainframe->select_tab(size_t(1));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
#ifndef __linux__
|
||||
mainframe->Thaw();
|
||||
#endif
|
||||
@@ -1814,10 +1829,10 @@ bool GUI_App::hot_reload_network_plugin()
|
||||
wxWindowDisabler disabler;
|
||||
|
||||
if (mainframe) {
|
||||
int current_tab = mainframe->m_tabpanel->GetSelection();
|
||||
if (current_tab == MainFrame::TabPosition::tpMonitor) {
|
||||
wxString current_tab = mainframe->m_tabpanel->GetSelectedPageName();
|
||||
if (current_tab == TAB_ID_MONITOR) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
|
||||
mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor);
|
||||
mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREPARE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2151,7 +2166,6 @@ void GUI_App::init_networking_callbacks()
|
||||
obj->is_tunnel_mqtt = tunnel;
|
||||
obj->command_request_push_all(true);
|
||||
obj->command_get_version();
|
||||
obj->erase_user_access_code();
|
||||
obj->command_get_access_code();
|
||||
if (m_agent)
|
||||
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
@@ -2201,7 +2215,6 @@ void GUI_App::init_networking_callbacks()
|
||||
wxString text;
|
||||
if (msg == "5") {
|
||||
obj->set_access_code("");
|
||||
obj->erase_user_access_code();
|
||||
text = wxString::Format(_L("Incorrect password"));
|
||||
wxGetApp().show_dialog(text);
|
||||
} else {
|
||||
@@ -2637,7 +2650,6 @@ void GUI_App::on_start_subscribe_again(std::string dev_id)
|
||||
|
||||
if ( (dev_id == obj->get_dev_id()) && obj->is_connecting() && obj->subscribe_counter > 0) {
|
||||
obj->subscribe_counter--;
|
||||
if(wxGetApp().getAgent()) wxGetApp().getAgent()->set_user_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": dev_id=" << obj->get_dev_id();
|
||||
}
|
||||
});
|
||||
@@ -2795,16 +2807,68 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
});
|
||||
};
|
||||
|
||||
// why: a newly loaded plugin only adds a selectable agent
|
||||
// refresh the dropdown and leave the live agent alone
|
||||
auto refresh_printer_agent_dropdown_after_load = [](const std::string&)
|
||||
{
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app]
|
||||
{
|
||||
if (!app->is_closing())
|
||||
app->refresh_printer_agent_dropdown();
|
||||
});
|
||||
};
|
||||
|
||||
// why: the unloaded plugin may have been the provider of the live agent
|
||||
// re-run selection, where a now-missing agent will be cleared
|
||||
// refresh dropdown after
|
||||
auto switch_printer_agent_after_unload = [](const std::string&)
|
||||
{
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app] {
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->switch_printer_agent();
|
||||
app->refresh_printer_agent_dropdown();
|
||||
});
|
||||
};
|
||||
|
||||
plugin_mgr.subscribe_on_unload_callback(PluginHostUi::close_windows_for_plugin);
|
||||
plugin_mgr.subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
|
||||
plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
|
||||
return;
|
||||
wxGetApp().mainframe->plugin_pages().on_plugin_register(plugin_key);
|
||||
});
|
||||
plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
|
||||
if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
|
||||
return;
|
||||
wxGetApp().mainframe->plugin_pages().on_plugin_deregister(plugin_key);
|
||||
});
|
||||
plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load);
|
||||
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
|
||||
plugin_mgr.subscribe_on_capability_load_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
[refresh_plugins_dialog, refresh_printer_agent_dropdown_after_load](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
refresh_printer_agent_dropdown_after_load(capability.plugin_key);
|
||||
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
|
||||
// current plate (on the UI thread) so the notification clears once its plugin is available.
|
||||
if (wxTheApp && !wxGetApp().is_closing())
|
||||
@@ -2812,12 +2876,17 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->revalidate_current_plate_if_plugins_missing();
|
||||
});
|
||||
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_register(capability);
|
||||
});
|
||||
plugin_mgr.subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_deregister(capability);
|
||||
refresh_plugins_dialog();
|
||||
switch_printer_agent_after_unload(capability.plugin_key);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2836,6 +2905,9 @@ bool GUI_App::on_init_inner()
|
||||
|
||||
::Label::initSysFont();
|
||||
|
||||
// Register wxInspector plugins for Orca custom controls
|
||||
RegisterOrcaInspectorPlugins();
|
||||
|
||||
// Set initialization of image handlers before any UI actions - See GH issue #7469
|
||||
wxInitAllImageHandlers();
|
||||
#ifdef NDEBUG
|
||||
@@ -3215,15 +3287,12 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
} */
|
||||
|
||||
copy_network_if_available();
|
||||
|
||||
if (scrn) {
|
||||
scrn->SetText(_L("Loading Plugins") + dots, 20);
|
||||
wxYield();
|
||||
}
|
||||
|
||||
on_init_network();
|
||||
|
||||
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
|
||||
// initialize() also installs the libslic3r hooks (capability resolver,
|
||||
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
|
||||
@@ -3252,6 +3321,9 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
}
|
||||
|
||||
copy_network_if_available();
|
||||
on_init_network();
|
||||
|
||||
if (m_agent)
|
||||
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
|
||||
|
||||
@@ -3324,7 +3396,7 @@ bool GUI_App::on_init_inner()
|
||||
mainframe = new MainFrame();
|
||||
// hide settings tabs after first Layout
|
||||
if (is_editor()) {
|
||||
mainframe->select_tab(size_t(0));
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
}
|
||||
|
||||
sidebar().obj_list()->init();
|
||||
@@ -3705,13 +3777,13 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||
// A failed install can leave the config naming a build that never made it to
|
||||
// disk (download_plugin() adopts the downloaded version up front so that
|
||||
// install_plugin() can name the library after it). If the whitelisted latest
|
||||
// is still installed, fall back to it instead of dropping the user into the
|
||||
// re-download flow without networking.
|
||||
// A failed install can leave the config naming a build that never made it to disk;
|
||||
// fall back to the installed latest instead of dropping the user into the re-download
|
||||
// flow. Only when the configured library is genuinely absent, though - a pinned series
|
||||
// that is on disk but failed to load once must keep its pin, not be rewritten for good.
|
||||
std::string latest = get_latest_network_version();
|
||||
if (config_version != latest && BBLNetworkPlugin::versioned_library_exists(latest)) {
|
||||
if (config_version != latest && !BBLNetworkPlugin::versioned_library_exists(config_version)
|
||||
&& BBLNetworkPlugin::versioned_library_exists(latest)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": falling back to installed " << latest;
|
||||
config_version = latest;
|
||||
app_config->set_network_plugin_version(latest);
|
||||
@@ -3815,7 +3887,14 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
}
|
||||
|
||||
if (should_load_networking_plugin && m_networking_compatible && !use_legacy_network_plugin()) {
|
||||
// A version pinned to something other than the latest series is a deliberate choice, so it
|
||||
// is exempt from the upgrade prompt the same way the legacy pin already is - otherwise the
|
||||
// dialog reappears on every launch for as long as the pin is held.
|
||||
const std::string pinned_version = app_config->get_network_plugin_version();
|
||||
const bool pinned_to_older_series = !pinned_version.empty() &&
|
||||
network_plugin_series(pinned_version) != network_plugin_series(get_latest_network_version());
|
||||
|
||||
if (should_load_networking_plugin && m_networking_compatible && !pinned_to_older_series) {
|
||||
app_config->clear_remind_network_update_later();
|
||||
|
||||
if (has_network_update_available()) {
|
||||
@@ -3849,6 +3928,48 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
|
||||
));
|
||||
}
|
||||
|
||||
void GUI_App::refresh_printer_agent_dropdown()
|
||||
{
|
||||
if (Tab* tab = get_tab(Preset::TYPE_PRINTER))
|
||||
{
|
||||
if (auto* printer_tab = dynamic_cast<TabPrinter*>(tab))
|
||||
printer_tab->refresh_printer_agent_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
|
||||
{
|
||||
if (!m_agent)
|
||||
return;
|
||||
|
||||
// why: tearing down the old machine selection is only ever the prefix of setting the live
|
||||
// agent (to a new one, or to null when the selection is missing) - so it lives here, not as
|
||||
// a standalone helper. Pass nullptr to clear the selection.
|
||||
if (DeviceManager* dev = getDeviceManager())
|
||||
{
|
||||
dev->set_selected_machine(""); // why: empty id disconnects and deselects the current machine
|
||||
m_agent->set_user_selected_machine("");
|
||||
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
|
||||
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
|
||||
dev->clear_other_devices(); // why: drop stale LAN discoveries; keep My Devices
|
||||
}
|
||||
|
||||
m_agent->set_printer_agent(agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
}
|
||||
|
||||
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
|
||||
{
|
||||
if (!stored_id.empty())
|
||||
return stored_id;
|
||||
return (preset_bundle && preset_bundle->is_bbl_vendor()) ? BBL_PRINTER_AGENT_ID : ORCA_PRINTER_AGENT_ID;
|
||||
}
|
||||
|
||||
std::string GUI_App::canonical_printer_agent_id(const std::string& picked_id)
|
||||
{
|
||||
return picked_id == resolve_printer_agent_id("") ? std::string() : picked_id;
|
||||
}
|
||||
|
||||
void GUI_App::switch_printer_agent()
|
||||
{
|
||||
if (!m_agent) {
|
||||
@@ -3856,24 +3977,17 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
// Read printer_agent from config, falling back to default
|
||||
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
|
||||
if (preset_bundle->is_bbl_vendor())
|
||||
effective_agent_id = BBL_PRINTER_AGENT_ID;
|
||||
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
|
||||
if (!value.empty())
|
||||
effective_agent_id = value;
|
||||
}
|
||||
const std::string effective_agent_id = resolve_printer_agent_id(config.opt_string("printer_agent"));
|
||||
|
||||
// Check if agent is registered
|
||||
const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id);
|
||||
if (!agent_info_ptr) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id
|
||||
<< "', keeping current agent";
|
||||
// Keep current agent, don't switch
|
||||
// why: the selected agent's provider is gone (e.g. plugin unloaded); leaving the old
|
||||
// live agent up would keep talking to a machine the user can no longer select.
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": agent ID '" << effective_agent_id
|
||||
<< "' is unregistered; clearing live printer agent";
|
||||
set_live_printer_agent(nullptr);
|
||||
return;
|
||||
}
|
||||
const PrinterAgentInfo agent_info = *agent_info_ptr;
|
||||
@@ -3887,7 +4001,9 @@ void GUI_App::switch_printer_agent()
|
||||
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
|
||||
|
||||
if (!new_printer_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id
|
||||
<< "'; clearing live printer agent";
|
||||
set_live_printer_agent(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3910,9 +4026,9 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
// Swap the agent; set_live_printer_agent resets the device selection so the new
|
||||
// agent starts clean (#124).
|
||||
set_live_printer_agent(new_printer_agent);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
@@ -4488,7 +4604,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
mainframe = new MainFrame();
|
||||
if (is_editor())
|
||||
// hide settings tabs after first Layout
|
||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
// Propagate model objects to object list.
|
||||
sidebar().obj_list()->init();
|
||||
//sidebar().aux_list()->init_auxiliary();
|
||||
@@ -6127,18 +6243,23 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
||||
}
|
||||
else if (msg == "unsigned_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio";
|
||||
MessageDialog
|
||||
msg_dlg(nullptr,
|
||||
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
||||
"Please go to your printer's settings and:\n"
|
||||
"1. Turn on LAN mode\n"
|
||||
"2. Enable Developer mode\n\n"
|
||||
"Developer mode allows the printer to work exclusively through local network access, "
|
||||
"enabling full functionality with OrcaSlicer."),
|
||||
_L("Network Plug-in Restriction"), wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
// Plugin re-emits this on every subscribe retry; latch it so it shows
|
||||
// once per connection episode.
|
||||
if (!m_show_error_msgdlg && !m_unsigned_plugin_warning_shown) {
|
||||
m_unsigned_plugin_warning_shown = true;
|
||||
MessageDialog
|
||||
msg_dlg(nullptr,
|
||||
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
||||
"Please go to your printer's settings and:\n"
|
||||
"1. Turn on LAN mode\n"
|
||||
"2. Enable Developer mode\n\n"
|
||||
"Developer mode allows the printer to work exclusively through local network access, "
|
||||
"enabling full functionality with OrcaSlicer."),
|
||||
_L("Network Plug-in Restriction"), wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8177,7 +8298,7 @@ bool GUI_App::show_modal_ip_address_enter_dialog(bool input_sn, wxString title)
|
||||
wxGetApp().app_config->save();
|
||||
|
||||
obj->set_dev_ip(ip_address.ToStdString());
|
||||
obj->set_user_access_code(access_code.ToStdString());
|
||||
obj->set_access_code(access_code.ToStdString());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -9742,7 +9863,7 @@ bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_b
|
||||
{
|
||||
reg_bin = L"";
|
||||
#ifdef WIN32
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_full.Exists()) {
|
||||
return false;
|
||||
}
|
||||
@@ -9768,8 +9889,8 @@ void GUI_App::associate_url(std::wstring url_prefix)
|
||||
|
||||
wxString key_string = "\"" + wbinary + "\" \"%1\"";
|
||||
|
||||
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_first(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix);
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_first.Exists()) {
|
||||
key_first.Create(false);
|
||||
}
|
||||
@@ -9789,7 +9910,7 @@ void GUI_App::disassociate_url(std::wstring url_prefix)
|
||||
#ifdef WIN32
|
||||
if (is_running_in_msix())
|
||||
return;
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
|
||||
if (!key_full.Exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -343,6 +343,7 @@ private:
|
||||
public:
|
||||
//try again when subscription fails
|
||||
void on_start_subscribe_again(std::string dev_id);
|
||||
void reset_unsigned_plugin_warning() { m_unsigned_plugin_warning_shown = false; }
|
||||
std::string get_local_models_path();
|
||||
bool OnInit() override;
|
||||
int OnExit() override;
|
||||
@@ -364,9 +365,14 @@ public:
|
||||
HMSQuery* get_hms_query() { return hms_query; }
|
||||
NetworkAgent* getAgent() { return m_agent; }
|
||||
|
||||
// Dynamic printer agent switching
|
||||
// Reconcile the live printer agent with the stored preset selection.
|
||||
void switch_printer_agent();
|
||||
|
||||
std::string resolve_printer_agent_id(const std::string& stored_id);
|
||||
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
|
||||
// then, all resolve and canonical would just be ORCA<->""
|
||||
std::string canonical_printer_agent_id(const std::string& picked_id);
|
||||
|
||||
FilamentColorCodeQuery* get_filament_color_code_query();
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
@@ -797,6 +803,11 @@ private:
|
||||
void window_pos_center(wxTopLevelWindow *window);
|
||||
bool select_language();
|
||||
|
||||
// Dynamic printer agent selection - internal helpers for switch_printer_agent
|
||||
// and the plugin load/unload callbacks (init_plugin_gui_wiring).
|
||||
void refresh_printer_agent_dropdown();
|
||||
void set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent); // null clears the selection
|
||||
|
||||
bool config_wizard_startup();
|
||||
void check_updates(const bool verbose);
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"sparse_infill_density", "", 1},
|
||||
{"fill_multiline", "", 1},
|
||||
{"sparse_infill_pattern", "", 1},
|
||||
{"sparse_infill_smooth_factor", "", 1},
|
||||
{"lateral_lattice_angle_1", "", 1},
|
||||
{"lateral_lattice_angle_2", "", 1},
|
||||
{"infill_overhang_angle", "", 1},
|
||||
@@ -533,11 +534,11 @@ void MenuFactory::append_menu_item_set_visible(wxMenu* menu)
|
||||
void MenuFactory::append_menu_item_delete(wxMenu* menu)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L("Del"), _L("Delete the selected object"),
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Del", "Keyboard Shortcut"), _L("Delete the selected object"),
|
||||
[](wxCommandEvent&) { plater()->remove_selected(); }, "menu_delete", nullptr,
|
||||
[]() { return plater()->can_delete(); }, m_parent);
|
||||
#else
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L("Backspace"), _L("Delete the selected object"),
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Backspace", "Keyboard Shortcut"), _L("Delete the selected object"),
|
||||
[](wxCommandEvent&) { plater()->remove_selected(); }, "", nullptr,
|
||||
[]() { return plater()->can_delete(); }, m_parent);
|
||||
#endif
|
||||
|
||||
@@ -3213,7 +3213,7 @@ void ObjectList::merge(bool to_multipart_object)
|
||||
//changed_object(obj_idx);
|
||||
//remove();
|
||||
}
|
||||
/* wxGetApp().plater()->load_model_objects(objects);
|
||||
// wxGetApp().plater()->load_model_objects(objects);
|
||||
|
||||
Selection& selection = p->view3D->get_canvas3d()->get_selection();
|
||||
size_t last_obj_idx = p->model.objects.size() - 1;
|
||||
|
||||
@@ -139,7 +139,7 @@ bool ObjectSettings::update_settings_list()
|
||||
optgroup->sidetext_width = 5;
|
||||
|
||||
optgroup->m_on_change = [this, config](const t_config_option_key& opt_id, const boost::any& value) {
|
||||
this->update_config_values(config);
|
||||
this->update_config_values(config, opt_id);
|
||||
wxGetApp().obj_list()->changed_object(); };
|
||||
|
||||
// call back for rescaling of the extracolumn control
|
||||
@@ -325,7 +325,7 @@ bool ObjectSettings::add_missed_options(ModelConfig* config_to, const DynamicPri
|
||||
return is_added;
|
||||
}
|
||||
|
||||
void ObjectSettings::update_config_values(ModelConfig* config)
|
||||
void ObjectSettings::update_config_values(ModelConfig* config, const std::string& changed_opt_key)
|
||||
{
|
||||
const auto objects_model = wxGetApp().obj_list()->GetModel();
|
||||
const auto item = wxGetApp().obj_list()->GetSelection();
|
||||
@@ -403,6 +403,10 @@ void ObjectSettings::update_config_values(ModelConfig* config)
|
||||
}
|
||||
|
||||
main_config.apply(config->get(), true);
|
||||
|
||||
if (printer_technology == ptFFF && changed_opt_key == "layer_height")
|
||||
config_manipulation.check_layer_height(&main_config);
|
||||
|
||||
printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) :
|
||||
config_manipulation.update_print_sla_config(&main_config) ;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
* we should add sparse_infill_pattern to avoid endless loop in update
|
||||
*/
|
||||
bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from);
|
||||
void update_config_values(ModelConfig *config);
|
||||
void update_config_values(ModelConfig *config, const std::string& changed_opt_key = "");
|
||||
void UpdateAndShow(const bool show);
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
@@ -223,7 +223,7 @@ bool ObjectTableSettings::update_settings_list(bool is_object, bool is_multiple_
|
||||
std::weak_ptr<ConfigOptionsGroup> weak_optgroup(optgroup);
|
||||
optgroup->m_on_change = [this, is_object, object, config, group_category](const t_config_option_key &opt_id, const boost::any &value) {
|
||||
this->m_parent->Freeze();
|
||||
this->update_config_values(is_object, object, config, group_category);
|
||||
this->update_config_values(is_object, object, config, group_category, opt_id);
|
||||
wxGetApp().obj_list()->changed_object();
|
||||
this->m_parent->Thaw();
|
||||
//update_extra_column_visible_status(optgroup.get(), cat.second, config);
|
||||
@@ -369,7 +369,7 @@ int ObjectTableSettings::update_extra_column_visible_status(ConfigOptionsGroup*
|
||||
return count;
|
||||
}
|
||||
|
||||
void ObjectTableSettings::update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category)
|
||||
void ObjectTableSettings::update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& changed_opt_key)
|
||||
{
|
||||
int different_count = 0;
|
||||
const auto printer_technology = wxGetApp().plater()->printer_technology();
|
||||
@@ -403,6 +403,9 @@ void ObjectTableSettings::update_config_values(bool is_object, ModelObject* obje
|
||||
|
||||
config_manipulation.set_is_BBL_Printer(wxGetApp().preset_bundle->is_bbl_vendor());
|
||||
|
||||
if (printer_technology == ptFFF && changed_opt_key == "layer_height")
|
||||
config_manipulation.check_layer_height(&main_config);
|
||||
|
||||
printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) :
|
||||
config_manipulation.update_print_sla_config(&main_config) ;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from);
|
||||
//return visible count
|
||||
int update_extra_column_visible_status(ConfigOptionsGroup* option_group, const std::vector<SimpleSettingData>& option_keys, ModelConfig* config);
|
||||
void update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category);
|
||||
void update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& changed_opt_key = "");
|
||||
void UpdateAndShow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category);
|
||||
void ValueChanged(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& key);
|
||||
void resetAllValues(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category);
|
||||
|
||||
@@ -548,7 +548,7 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
const char* css =
|
||||
"button {"
|
||||
"button, button:hover, button:active, button:focus {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
@@ -589,6 +589,58 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveInputBorder(wxWindow* win)
|
||||
{
|
||||
GtkWidget* widget = win->GetHandle();
|
||||
if (!widget) return;
|
||||
|
||||
#if GTK_CHECK_VERSION(3, 0, 0)
|
||||
// GTK3+: use CSS provider
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
// Target 'entry' and its inner subnodes (like text selection areas)
|
||||
const char* css =
|
||||
"entry, entry text, entry undershoot {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
" padding: 0px;"
|
||||
" margin: 0px;"
|
||||
" min-height: 0px;"
|
||||
" min-width: 0px;"
|
||||
" background: none;"
|
||||
"}";
|
||||
|
||||
#if GTK_CHECK_VERSION(4, 0, 0)
|
||||
// GTK4
|
||||
gtk_css_provider_load_from_data(provider, css, -1);
|
||||
#else
|
||||
// GTK3
|
||||
gtk_css_provider_load_from_data(provider, css, -1, nullptr);
|
||||
#endif
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(widget);
|
||||
gtk_style_context_add_provider(
|
||||
ctx,
|
||||
GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER
|
||||
);
|
||||
g_object_unref(provider);
|
||||
|
||||
#else
|
||||
// GTK2: Target the x/y thickness of the entry widget
|
||||
gtk_rc_parse_string(
|
||||
"style \"no-padding-entry\" {"
|
||||
" xthickness = 0"
|
||||
" ythickness = 0"
|
||||
" GtkEntry::inner-border = { 0, 0, 0, 0 }"
|
||||
" GtkEntry::focus-line-width = 0"
|
||||
"}"
|
||||
"class \"GtkEntry\" style \"no-padding-entry\""
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif // __WXGTK__
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <wx/settings.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/inspector/inspector.h>
|
||||
|
||||
#include <chrono>
|
||||
#include "Event.hpp"
|
||||
@@ -88,7 +89,7 @@ void update_dark_ui(wxWindow* window);
|
||||
|
||||
extern std::deque<wxDialog*> dialogStack;
|
||||
|
||||
template<class P> class DPIAware : public P
|
||||
template<class P> class DPIAware : public P, public wxInspector::wxInspectable
|
||||
{
|
||||
public:
|
||||
DPIAware(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition,
|
||||
@@ -107,18 +108,12 @@ public:
|
||||
this->SetFont(m_normal_font);
|
||||
#endif
|
||||
this->CenterOnParent();
|
||||
SetupInspectorAccelerator(this);
|
||||
#ifdef _WIN32
|
||||
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();
|
||||
|
||||
@@ -182,6 +177,11 @@ public:
|
||||
|
||||
float scale_factor() const { return m_scale_factor; }
|
||||
float prev_scale_factor() const { return m_prev_scale_factor; }
|
||||
// Only meant to be used by inspector, not public API
|
||||
void set_scale_factor(float v) { m_scale_factor = v; }
|
||||
void set_prev_scale_factor(float v) { m_prev_scale_factor = v; }
|
||||
void set_em_unit(int v) { m_em_unit = v; }
|
||||
bool force_rescale() const { return m_force_rescale; }
|
||||
|
||||
int em_unit() const { return m_em_unit; }
|
||||
// int font_size() const { return m_font_size; }
|
||||
@@ -228,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; }
|
||||
|
||||
@@ -240,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);
|
||||
@@ -465,8 +477,9 @@ void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||
void staticbox_remove_margin(wxStaticBox* sb);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
void RemoveButtonBorder(wxWindow* win);
|
||||
#ifdef __WXGTK__
|
||||
void RemoveButtonBorder(wxWindow* win); // for wxButton/wxBitmapToggleButton based controls (SwitchButton, CheckBox)
|
||||
void RemoveInputBorder(wxWindow* win); // for TextCtrl based controls (TextInput, ComboBox, SpinInput..)
|
||||
#endif
|
||||
|
||||
#if defined(__WXOSX__) || defined(__linux__)
|
||||
|
||||
@@ -372,7 +372,7 @@ bool GLGizmoAdvancedCut::on_init()
|
||||
|
||||
std::string GLGizmoAdvancedCut::on_get_name() const
|
||||
{
|
||||
return (_(L("Cut"))).ToUTF8().data();
|
||||
return (_L_CONTEXT("Cut", "Cut tool")).ToUTF8().data();
|
||||
}
|
||||
|
||||
void GLGizmoAdvancedCut::on_load(cereal::BinaryInputArchive &ar)
|
||||
@@ -1905,7 +1905,7 @@ bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, fl
|
||||
mean_size *= float(units_mm_to_in);
|
||||
min_size *= float(units_mm_to_in);
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ std::string GLGizmoAssembly::on_get_name() const
|
||||
{
|
||||
if (!on_is_activable() && m_state == EState::Off) {
|
||||
if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
||||
return _u8L("Assemble") + ":\n" + _u8L("Please confirm explosion ratio = 1 and select at least two volumes.");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool") + ":\n" + _u8L("Please confirm explosion ratio = 1 and select at least two volumes.");
|
||||
}
|
||||
else {
|
||||
return _u8L("Assemble") + ":\n" + _u8L("Please select at least two volumes.");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool") + ":\n" + _u8L("Please select at least two volumes.");
|
||||
}
|
||||
} else {
|
||||
return _u8L("Assemble");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
bool is_leaving = mouse_event.Leaving() && !m_parent.has_mouse_capture(); // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
|
||||
@@ -15,6 +15,8 @@ static const ColorRGBA DEF_COLOR = {0.7f, 0.7f, 0.7f, 1.f};
|
||||
static const ColorRGBA SELECTED_COLOR = {0.0f, 0.5f, 0.5f, 1.0f};
|
||||
static const ColorRGBA ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f};
|
||||
static const ColorRGBA HOVER_COLOR = {0.7f, 0.7f, 0.7f, 0.5f};
|
||||
static constexpr float BRIM_EAR_RADIUS_MIN = 0.1f;
|
||||
static constexpr float BRIM_EAR_RADIUS_MAX = 100.f;
|
||||
|
||||
static ModelVolume *get_model_volume(const Selection &selection, Model &model)
|
||||
{
|
||||
@@ -41,14 +43,14 @@ GLGizmoBrimEars::GLGizmoBrimEars(GLCanvas3D &parent, const std::string &icon_fil
|
||||
|
||||
bool GLGizmoBrimEars::on_init()
|
||||
{
|
||||
m_new_point_head_diameter = get_brim_default_radius();
|
||||
m_new_point_head_radius = get_brim_default_radius();
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_E;
|
||||
|
||||
const wxString ctrl = GUI::shortkey_ctrl_prefix();
|
||||
const wxString alt = GUI::shortkey_alt_prefix();
|
||||
|
||||
m_desc["head_diameter"] = _L("Head diameter");
|
||||
m_desc["brim_ear_radius"] = _L("Brim ear radius");
|
||||
m_desc["max_angle"] = _L("Max angle");
|
||||
m_desc["detection_radius"] = _L("Detection radius");
|
||||
m_desc["remove"] = _L("Remove");
|
||||
@@ -62,7 +64,7 @@ bool GLGizmoBrimEars::on_init()
|
||||
m_shortcuts = {
|
||||
{_L("Left mouse button"), _L("Add or Select")},
|
||||
{_L("Right mouse button"), _L("Remove")},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["head_diameter"]},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
|
||||
{alt + _L("Mouse wheel"), m_desc["section_view"]},
|
||||
};
|
||||
|
||||
@@ -358,7 +360,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix_no_offset().inverse();
|
||||
std::pair<Vec3f, Vec3f> pos_and_normal;
|
||||
if (unproject_on_mesh2(mouse_position, pos_and_normal)) {
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_diameter / 2.f), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_radius), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
} else {
|
||||
render_hover_point.reset();
|
||||
}
|
||||
@@ -397,7 +399,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// brim ear always face up
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add brim ear");
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2.f, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
m_parent.set_as_dirty();
|
||||
m_wait_for_up_event = true;
|
||||
find_single();
|
||||
@@ -490,9 +492,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
// mouse wheel up
|
||||
if (action == SLAGizmoEventType::MouseWheelUp) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::min(20., initial_value + 0.1);
|
||||
m_new_point_head_radius = std::min(BRIM_EAR_RADIUS_MAX, initial_value + 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -502,9 +504,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
|
||||
if (action == SLAGizmoEventType::MouseWheelDown) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::max(5., initial_value - 0.1);
|
||||
m_new_point_head_radius = std::max(BRIM_EAR_RADIUS_MIN, initial_value - 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -597,18 +599,18 @@ std::vector<const ConfigOption *> GLGizmoBrimEars::get_config_options(const std:
|
||||
|
||||
void GLGizmoBrimEars::begin_radius_change(float initial_value)
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f)
|
||||
m_old_point_head_diameter = initial_value;
|
||||
if (m_old_point_head_radius == 0.f)
|
||||
m_old_point_head_radius = initial_value;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::update_cache_radius()
|
||||
{
|
||||
if (render_hover_point)
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_radius;
|
||||
|
||||
for (auto &cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected) {
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_radius;
|
||||
find_single();
|
||||
update_model_object();
|
||||
}
|
||||
@@ -617,18 +619,18 @@ void GLGizmoBrimEars::update_cache_radius()
|
||||
|
||||
void GLGizmoBrimEars::apply_radius_change()
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f) return;
|
||||
if (m_old_point_head_radius == 0.f) return;
|
||||
|
||||
// momentarily restore the old value to take snapshot
|
||||
for (auto& cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected)
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_diameter / 2.f;
|
||||
float backup = m_new_point_head_diameter;
|
||||
m_new_point_head_diameter = m_old_point_head_diameter;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change point head diameter");
|
||||
m_new_point_head_diameter = backup;
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_radius;
|
||||
float backup = m_new_point_head_radius;
|
||||
m_new_point_head_radius = m_old_point_head_radius;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change brim ear radius");
|
||||
m_new_point_head_radius = backup;
|
||||
update_cache_radius();
|
||||
m_old_point_head_diameter = 0.f;
|
||||
m_old_point_head_radius = 0.f;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limit)
|
||||
@@ -653,7 +655,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
float space_size = m_imgui->get_style_scaling() * 8;
|
||||
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
m_desc["create"], m_desc["remove"]};
|
||||
float widest_text = m_imgui->find_widest_text(text_list);
|
||||
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
|
||||
@@ -680,11 +682,11 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
|
||||
// - take correct undo/redo snapshot after the user is done with moving the slider
|
||||
ImGui::AlignTextToFramePadding();
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
m_imgui->text(m_desc["head_diameter"]);
|
||||
float initial_value = m_new_point_head_radius;
|
||||
m_imgui->text(m_desc["brim_ear_radius"]);
|
||||
ImGui::SameLine(caption_size);
|
||||
ImGui::PushItemWidth(slider_width);
|
||||
m_imgui->bbl_slider_float_style("##head_diameter", &m_new_point_head_diameter, 5, 20, "%.1f", 1.0f, true);
|
||||
m_imgui->bbl_slider_float_style("##brim_ear_radius", &m_new_point_head_radius, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f", 1.0f, true);
|
||||
if (m_imgui->get_last_slider_status().clicked) {
|
||||
begin_radius_change(initial_value);
|
||||
}
|
||||
@@ -695,7 +697,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
}
|
||||
ImGui::SameLine(drag_left_width);
|
||||
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
||||
ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f");
|
||||
ImGui::BBLDragFloat("##brim_ear_radius_input", &m_new_point_head_radius, 0.05f, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -910,9 +912,9 @@ void GLGizmoBrimEars::on_stop_dragging()
|
||||
m_point_before_drag = CacheEntry();
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::select_point(int i)
|
||||
{
|
||||
@@ -920,11 +922,11 @@ void GLGizmoBrimEars::select_point(int i)
|
||||
for (auto &point_and_selection : m_editing_cache) point_and_selection.selected = (i == AllPoints);
|
||||
m_selection_empty = (i == NoPoints);
|
||||
|
||||
if (i == AllPoints) m_new_point_head_diameter = m_editing_cache[0].brim_point.head_front_radius * 2.f;
|
||||
if (i == AllPoints) m_new_point_head_radius = m_editing_cache[0].brim_point.head_front_radius;
|
||||
} else {
|
||||
m_editing_cache[i].selected = true;
|
||||
m_selection_empty = false;
|
||||
m_new_point_head_diameter = m_editing_cache[i].brim_point.head_front_radius * 2.f;
|
||||
m_new_point_head_radius = m_editing_cache[i].brim_point.head_front_radius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1011,8 +1013,7 @@ void GLGizmoBrimEars::auto_generate()
|
||||
auto add_point = [this, &trsf, &normal](const Point &p) {
|
||||
Vec3d world_pos = {float(p.x() * SCALING_FACTOR), float(p.y() * SCALING_FACTOR), -0.0001};
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// m_editing_cache.emplace_back(BrimPoint(object_pos.cast<float>(), m_new_point_head_diameter / 2), false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2, false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, normal);
|
||||
};
|
||||
for (const ExPolygon &ex_poly : m_first_layer) {
|
||||
Polygon out_poly = ex_poly.contour;
|
||||
@@ -1158,8 +1159,11 @@ void GLGizmoBrimEars::reset_all_pick() { std::map<GLVolume *, std::shared_ptr<Pi
|
||||
float GLGizmoBrimEars::get_brim_default_radius() const
|
||||
{
|
||||
const double nozzle_diameter = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const DynamicPrintConfig &pring_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return pring_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 16.0f;
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return std::clamp(
|
||||
float(print_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 8.0),
|
||||
BRIM_EAR_RADIUS_MIN,
|
||||
BRIM_EAR_RADIUS_MAX);
|
||||
}
|
||||
|
||||
ExPolygon GLGizmoBrimEars::make_polygon(BrimPoint point, const Geometry::Transformation &trsf)
|
||||
|
||||
@@ -98,12 +98,12 @@ private:
|
||||
|
||||
void render_points(const Selection& selection);
|
||||
|
||||
float m_new_point_head_diameter; // Size of a new point.
|
||||
float m_new_point_head_radius; // Radius of a new point.
|
||||
float m_max_angle = 125.f;
|
||||
float m_detection_radius = 1.f;
|
||||
double m_detection_radius_max = .0f;
|
||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||
float m_old_point_head_diameter = 0.; // the same
|
||||
float m_old_point_head_radius = 0.; // the same
|
||||
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo
|
||||
std::map<int, CacheEntry> m_single_brim;
|
||||
ObjectID m_old_mo_id;
|
||||
|
||||
@@ -241,7 +241,7 @@ std::string GLGizmoCut3D::get_tooltip() const
|
||||
std::string tooltip;
|
||||
if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) {
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
std::string unit_str = " " + (m_imperial_units ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
std::string unit_str = " " + (m_imperial_units ? _u8L_CONTEXT("in", "inches") : _u8L("mm"));
|
||||
const BoundingBoxf3& tbb = m_transformed_bounding_box;
|
||||
|
||||
const std::string name = m_keep_as_parts ? _u8L("Part") : _u8L("Object");
|
||||
@@ -541,7 +541,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i
|
||||
ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
|
||||
|
||||
ImGui::SameLine();
|
||||
m_imgui->text(m_imperial_units ? _CTX("in", "inches") : _L("mm"));
|
||||
m_imgui->text(m_imperial_units ? _L_CONTEXT("in", "inches") : _L("mm"));
|
||||
|
||||
value_in = value * (m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0);
|
||||
return !is_approx(old_val, value);
|
||||
@@ -582,7 +582,7 @@ bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& valu
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
@@ -653,7 +653,7 @@ bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str());
|
||||
|
||||
@@ -1386,7 +1386,7 @@ void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const
|
||||
|
||||
std::string GLGizmoCut3D::on_get_name() const
|
||||
{
|
||||
return _u8L("Cut");
|
||||
return _u8L_CONTEXT("Cut", "Cut tool");
|
||||
}
|
||||
|
||||
void GLGizmoCut3D::apply_color_clip_plane_colors()
|
||||
@@ -2472,7 +2472,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
|
||||
void GLGizmoCut3D::render_build_size()
|
||||
{
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
wxString unit_str = m_imperial_units ? _CTX("in", "inches") : _L("mm");
|
||||
wxString unit_str = m_imperial_units ? _L_CONTEXT("in", "inches") : _L("mm");
|
||||
Vec3d tbb_sz = m_transformed_bounding_box.size() * koef; // ORCA
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@@ -3502,7 +3502,7 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
||||
names += ", " + from_u8(err_objects_names[i]);
|
||||
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
||||
"Some connectors may be missing in slicing result.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."), names)).ShowModal();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -1961,7 +1964,7 @@ void GLGizmoEmboss::draw_model_type()
|
||||
|
||||
ImGui::SameLine();
|
||||
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
|
||||
if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
if (ImGui::RadioButton(_u8L_CONTEXT(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
new_type = negative;
|
||||
else if (ImGui::IsItemHovered()) {
|
||||
if (is_last_solid_part)
|
||||
@@ -2811,29 +2814,29 @@ void GLGizmoEmboss::draw_advanced()
|
||||
ImGui::SameLine(input_offset);
|
||||
if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Left", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Center", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Center", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Right", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.x * 2.f); // ORCA use wider spacing for separation between horizontal / vertical alignment
|
||||
|
||||
if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Top", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Middle", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Bottom", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
return is_change;
|
||||
};
|
||||
const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr;
|
||||
|
||||
@@ -455,8 +455,8 @@ bool GLGizmoMeasure::on_init()
|
||||
m_shortcuts = {
|
||||
{_L("Left mouse button"), _L("Select")},
|
||||
{shift + _L("Left mouse button"), _L("Select point")},
|
||||
{_L("Delete"), _L("Restart selection")},
|
||||
{_L("Esc"), _L("Cancel a feature until exit")},
|
||||
{_L_CONTEXT("Delete", "Keyboard Shortcut"), _L("Restart selection")},
|
||||
{_L_CONTEXT("Esc", "Keyboard Shortcut"), _L("Cancel a feature until exit")},
|
||||
};
|
||||
|
||||
return true;
|
||||
@@ -1252,7 +1252,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
const bool use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance;
|
||||
const std::string curr_value_str = format_double(curr_value);
|
||||
const std::string units = use_inches ? _CTX_utf8("in", "inches") : _u8L("mm");
|
||||
const std::string units = use_inches ? _u8L_CONTEXT("in", "inches") : _u8L("mm");
|
||||
const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x;
|
||||
static double edit_value = 0.0;
|
||||
|
||||
@@ -1303,7 +1303,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
return;
|
||||
|
||||
const double ratio = new_value / old_value;
|
||||
wxGetApp().plater()->take_snapshot(_CTX_utf8("Scale", "Verb"), UndoRedo::SnapshotType::GizmoAction);
|
||||
wxGetApp().plater()->take_snapshot(_u8L_CONTEXT("Scale", "Verb"), UndoRedo::SnapshotType::GizmoAction);
|
||||
// apply scale
|
||||
TransformationType type;
|
||||
type.set_world();
|
||||
@@ -1359,10 +1359,10 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGuiWrapper::push_confirm_button_style();
|
||||
//if (m_imgui->button(_CTX(L_CONTEXT("Scale part", "Verb"), "Verb")))
|
||||
//if (m_imgui->button(_L_CONTEXT(L_CONTEXT("Scale part", "Verb"), "Verb")))
|
||||
//action_scale(edit_value, curr_value, true);
|
||||
//ImGui::SameLine();
|
||||
if (m_imgui->button(_CTX(L_CONTEXT("Scale all", "Verb"), "Verb")))
|
||||
if (m_imgui->button(_L_CONTEXT(L_CONTEXT("Scale all", "Verb"), "Verb")))
|
||||
action_scale(edit_value, curr_value, false);
|
||||
ImGuiWrapper::pop_confirm_button_style();
|
||||
ImGui::SameLine();
|
||||
@@ -2130,7 +2130,7 @@ void GLGizmoMeasure::show_face_face_assembly_senior()
|
||||
void GLGizmoMeasure::init_render_input_window()
|
||||
{
|
||||
m_use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
m_units = " " + (m_use_inches ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
m_units = " " + (m_use_inches ? _u8L_CONTEXT("in", "inches") : _u8L("mm"));
|
||||
m_space_size = ImGui::CalcTextSize(" ").x * 2;
|
||||
m_input_size_max = ImGui::CalcTextSize("-100.00").x * 1.2;
|
||||
m_same_model_object = is_two_volume_in_same_model_object();
|
||||
|
||||
@@ -122,7 +122,7 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
std::pair<wxString, wxString> paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
|
||||
std::pair<wxString, wxString> erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
|
||||
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
|
||||
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L("Enter"), m_desc["toggle_wireframe"]};
|
||||
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]};
|
||||
|
||||
m_shortcuts_brush = {
|
||||
paint_shortcut,
|
||||
|
||||
@@ -1704,7 +1704,7 @@ void GLGizmoSVG::draw_size()
|
||||
|
||||
if (m_keep_ratio) {
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? _CTX("in", "inches") : _L("mm"));
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? _L_CONTEXT("in", "inches") : _L("mm"));
|
||||
|
||||
ImGui::SameLine(m_gui_cfg->input_offset);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
@@ -2016,7 +2016,7 @@ void GLGizmoSVG::draw_model_type()
|
||||
ImGui::SameLine();
|
||||
|
||||
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
|
||||
if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
if (ImGui::RadioButton(_u8L_CONTEXT(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
new_type = negative;
|
||||
else if (ImGui::IsItemHovered()) {
|
||||
if (is_last_solid_part)
|
||||
|
||||
@@ -143,9 +143,9 @@ bool GLGizmoScale3D::on_init()
|
||||
std::string GLGizmoScale3D::on_get_name() const
|
||||
{
|
||||
if (!on_is_activable() && m_state == EState::Off) {
|
||||
return _CTX_utf8("Scale", "Verb") + ":\n" + _u8L("Please select at least one object.");
|
||||
return _u8L_CONTEXT("Scale", "Verb") + ":\n" + _u8L("Please select at least one object.");
|
||||
} else {
|
||||
return _CTX_utf8("Scale", "Verb");
|
||||
return _u8L_CONTEXT("Scale", "Verb");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1213,8 +1213,8 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog()
|
||||
shortcuts.push_back(std::make_pair(_L"Del", _L("Remove selected points")));
|
||||
shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane")));
|
||||
shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane")));
|
||||
shortcuts.push_back(std::make_pair(_L("Enter"), _L("Apply changes")));
|
||||
shortcuts.push_back(std::make_pair(_L("Esc"), _L("Discard changes")));
|
||||
shortcuts.push_back(std::make_pair(_L_CONTEXT("Enter", "Keyboard Shortcut"), _L("Apply changes")));
|
||||
shortcuts.push_back(std::make_pair(_L_CONTEXT("Esc", "Keyboard Shortcut"), _L("Discard changes")));
|
||||
shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode")));
|
||||
shortcuts.push_back(std::make_pair("A", _L("Auto-generate points")));
|
||||
|
||||
|
||||
@@ -1161,7 +1161,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
imgui_wrapper->calc_text_size(_L("Part")).x
|
||||
}) + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
|
||||
float label_max = std::max({
|
||||
imgui_wrapper->calc_text_size(_CTX("Scale", "Noun")).x,
|
||||
imgui_wrapper->calc_text_size(_L_CONTEXT("Scale", "Noun")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Size")).x
|
||||
});
|
||||
float caption_max = std::max(label_max, coord_combo_width - 3 * space_size);
|
||||
@@ -1218,7 +1218,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
|
||||
//ImGui::PushItemWidth(unit_size * 2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(_CTX("Scale", "Noun"));
|
||||
imgui_wrapper->text(_L_CONTEXT("Scale", "Noun"));
|
||||
ImGui::SameLine(caption_max + space_size);
|
||||
ImGui::PushItemWidth(unit_size);
|
||||
ImGui::BBLInputDouble(label_scale_values[0][0], &scale[0], 0.0f, 0.0f, "%.2f");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "HMS.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "DeviceManager.hpp"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevUtil.h"
|
||||
|
||||
+16
-13
@@ -1,7 +1,6 @@
|
||||
#ifndef slic3r_HMS_hpp_
|
||||
#define slic3r_HMS_hpp_
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
@@ -11,7 +10,11 @@
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -26,12 +29,12 @@ namespace GUI {
|
||||
class HMSQuery {
|
||||
|
||||
protected:
|
||||
std::unordered_map<string, json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
||||
std::unordered_map<string, json> m_hms_action_jsons;// key-> device id type
|
||||
std::unordered_map<std::string, nlohmann::json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
||||
std::unordered_map<std::string, nlohmann::json> m_hms_action_jsons;// key-> device id type
|
||||
std::unordered_map<wxString, wxImage> m_hms_local_images; // key-> image name
|
||||
mutable std::mutex m_hms_mutex;
|
||||
|
||||
std::unordered_map<string, time_t> m_cloud_hms_last_update_time;
|
||||
std::unordered_map<std::string, std::time_t> m_cloud_hms_last_update_time;
|
||||
|
||||
public:
|
||||
HMSQuery() { }
|
||||
@@ -61,18 +64,18 @@ private:
|
||||
// load hms
|
||||
void init_hms_info(const std::string& dev_type_id);
|
||||
void copy_from_data_dir_to_local();
|
||||
int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, json* receive_json);
|
||||
int load_from_local(const std::string& hms_type, const std::string& dev_id_type, json* receive_json, std::string& version_info);
|
||||
int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, json save_json);
|
||||
int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json);
|
||||
int load_from_local(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json, std::string& version_info);
|
||||
int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, nlohmann::json save_json);
|
||||
std::string get_hms_file(std::string hms_type, std::string lang = std::string("en"), std::string dev_id_type = "");
|
||||
|
||||
// internal query
|
||||
string get_dev_id_type(const MachineObject* obj) const;
|
||||
wxString _query_hms_msg(const string& dev_id_type, const string& long_error_code, const string& lang_code = std::string("en"));
|
||||
std::string get_dev_id_type(const MachineObject* obj) const;
|
||||
wxString _query_hms_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
|
||||
bool _is_internal_error(const string &dev_id_type, const string &long_error_code, const string &lang_code = std::string("en"));
|
||||
wxString _query_error_msg(const string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
wxString _query_error_image_action(const string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
||||
bool _is_internal_error(const std::string &dev_id_type, const std::string &long_error_code, const std::string &lang_code = std::string("en"));
|
||||
wxString _query_error_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
|
||||
wxString _query_error_image_action(const std::string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
||||
};
|
||||
|
||||
int get_hms_info_version(std::string &version);
|
||||
@@ -85,4 +88,4 @@ std::string get_error_message(int error_code);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#define _u8L(s) Slic3r::GUI::I18N::translate_utf8((s))
|
||||
#endif /* _ */
|
||||
|
||||
#ifndef _CTX
|
||||
#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
|
||||
#define _CTX_utf8(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
|
||||
#ifndef _L_CONTEXT
|
||||
#define _L_CONTEXT(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
|
||||
#define _u8L_CONTEXT(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
|
||||
#endif /* _ */
|
||||
|
||||
#ifndef L
|
||||
|
||||
@@ -1733,7 +1733,7 @@ std::string IMSlider::get_label(int tick, LabelType label_type)
|
||||
::sprintf(layer_height, "%.2f", m_values.empty() ? m_label_koef * value : m_values[value]);
|
||||
if (label_type == ltHeight) return std::string(layer_height);
|
||||
if (label_type == ltHeightWithLayer) {
|
||||
char buffer[64];
|
||||
char buffer[90];
|
||||
size_t layer_number;
|
||||
layer_number = m_draw_mode == dmSequentialFffPrint ? (m_values.empty() ? value : value + 1) : m_is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1);
|
||||
::sprintf(buffer, "%5s\n%5s", std::to_string(layer_number).c_str(), layer_height);
|
||||
|
||||
@@ -2813,12 +2813,26 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
}
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
ImFontConfig fallback_cfg = cfg;
|
||||
fallback_cfg.MergeMode = true;
|
||||
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
|
||||
}
|
||||
|
||||
bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + font_name_bold).c_str(), m_font_size, &cfg, ranges.Data);
|
||||
if (bold_font == nullptr) {
|
||||
bold_font = io.Fonts->AddFontDefault();
|
||||
if (bold_font == nullptr) { throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); }
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
ImFontConfig fallback_cfg = cfg;
|
||||
fallback_cfg.MergeMode = true;
|
||||
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
default_font->Scale *= 1.25f;
|
||||
bold_font->Scale *= 1.25f;
|
||||
|
||||
@@ -644,7 +644,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
|
||||
if (m_file_sys->GetFileType() == PrinterFileSystem::F_MODEL) {
|
||||
if (secondAction != _L("Play"))
|
||||
thirdAction = secondAction;
|
||||
secondAction = _CTX("Print", "Verb");
|
||||
secondAction = _L_CONTEXT("Print", "Verb");
|
||||
}
|
||||
// Draw buttons on hovered item
|
||||
wxRect rect{pt.x, pt.y + m_content_rect.GetBottom() - m_buttons_background.GetHeight(), m_content_rect.GetWidth(), m_buttons_background.GetHeight()};
|
||||
|
||||
@@ -548,7 +548,7 @@ void ArrangeJob::process(Ctl &ctl)
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this, &ctl](unsigned num_finished, std::string str = "") {
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging") + str);
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging ") + str);
|
||||
};
|
||||
|
||||
{
|
||||
|
||||
@@ -45,24 +45,26 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
||||
for (const ExPolygon &shape : shapes)
|
||||
bounding_box.merge(BoundingBox(shape.contour.points));
|
||||
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
|
||||
|
||||
// calculate conversion from FontPoint to screen pixels by size of font
|
||||
double scale = get_text_shape_scale(item.prop, *item.font.font_file) * m_input.ppm;
|
||||
scales[index] = scale;
|
||||
|
||||
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
|
||||
BoundingBoxf bb2(bounding_box.min.cast<double>(),
|
||||
bounding_box.max.cast<double>());
|
||||
if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1)
|
||||
continue; // or however the font job's degenerate-box case is handled
|
||||
|
||||
// Normalize to fit max_size, exactly like CreateFontImageJob does against m_input.size.
|
||||
// Fit by height (matches row height), then clamp width if needed.
|
||||
constexpr float preview_padding_px = 2.f; // margin for AA sampling, tune to your AA kernel radius
|
||||
|
||||
double scale = m_input.max_size.y() / (double) bounding_box.size().y();
|
||||
BoundingBoxf bb2(bounding_box.min.cast<double>(), bounding_box.max.cast<double>());
|
||||
bb2.scale(scale);
|
||||
image.tex_size.x = std::ceil(bb2.max.x() - bb2.min.x());
|
||||
image.tex_size.y = std::ceil(bb2.max.y() - bb2.min.y());
|
||||
|
||||
// crop image width
|
||||
if (image.tex_size.x > m_input.max_size.x())
|
||||
// crop width only if the (now height-normalized) text is too wide
|
||||
image.tex_size.x = std::ceil(bb2.max.x() - bb2.min.x()) + 2 * preview_padding_px;
|
||||
image.tex_size.y = std::ceil(bb2.max.y() - bb2.min.y()) + 2 * preview_padding_px;
|
||||
|
||||
if (image.tex_size.x > m_input.max_size.x())
|
||||
image.tex_size.x = m_input.max_size.x();
|
||||
// crop image height
|
||||
if (image.tex_size.y > m_input.max_size.y())
|
||||
image.tex_size.y = m_input.max_size.y();
|
||||
|
||||
scales[index] = scale;
|
||||
}
|
||||
|
||||
// arrange bounding boxes
|
||||
|
||||
@@ -149,6 +149,46 @@ void OrientJob::prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// parameters to minimize support area
|
||||
static void setMinimalSupportAreaPrams(Slic3r::orientation::OrientParams &out)
|
||||
{
|
||||
out.TAR_A = 0.015f;
|
||||
out.TAR_B = 0.177f;
|
||||
out.RELATIVE_F = 20;
|
||||
out.CONTOUR_F = 0.5f;
|
||||
out.BOTTOM_F = 2.5f;
|
||||
out.BOTTOM_HULL_F = 0.1f;
|
||||
out.TAR_C = 0.1f;
|
||||
out.TAR_D = 1;
|
||||
out.TAR_E = 0.0115f;
|
||||
out.FIRST_LAY_H = 0.2f; // 0.0475;
|
||||
out.VECTOR_TOL = -0.00083f;
|
||||
out.NEGL_FACE_SIZE = 0.01f;
|
||||
out.ASCENT = -0.5f;
|
||||
out.PLAFOND_ADV = 0.0599f;
|
||||
out.CONTOUR_AMOUNT = 0.0182427f;
|
||||
out.OV_H = 2.574f;
|
||||
out.height_offset = 2.3728f;
|
||||
out.height_log = 0.041375f;
|
||||
out.height_log_k = 1.9325457f;
|
||||
out.LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f
|
||||
out.LAF_MIN = 0.97f; // cos(14\degree) 0.9703f
|
||||
out.TAR_LAF = 0.001f; // 0.01f
|
||||
out.TAR_PROJ_AREA = 0.1f;
|
||||
out.BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable
|
||||
out.BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help)
|
||||
out.height_to_bottom_hull_ratio_MIN = 1,
|
||||
out.BOTTOM_HULL_MAX = 2000; // max bottom hull area
|
||||
out.APPERANCE_FACE_SUPP = 3; // penalty of generating supports on appearance face
|
||||
out.overhang_angle = 60.f;
|
||||
out.use_low_angle_face = true;
|
||||
out.min_volume = false;
|
||||
out.fun_dir = {};
|
||||
out.parallel = true;
|
||||
out.progressind = {};
|
||||
out.stopcondition = {};
|
||||
}
|
||||
|
||||
void OrientJob::process(Ctl &ctl)
|
||||
{
|
||||
static const auto arrangestr = _u8L("Orienting...");
|
||||
@@ -161,9 +201,8 @@ void OrientJob::process(Ctl &ctl)
|
||||
const GLCanvas3D::OrientSettings& settings = m_plater->canvas3D()->get_orient_settings();
|
||||
|
||||
orientation::OrientParams params;
|
||||
orientation::OrientParamsArea params_area;
|
||||
if (settings.min_area) {
|
||||
memcpy(¶ms, ¶ms_area, sizeof(params));
|
||||
setMinimalSupportAreaPrams(params);
|
||||
params.min_volume = false;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -201,7 +201,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
#ifdef __APPLE__
|
||||
{"fn+⌫", L("Delete Selected")},
|
||||
#else
|
||||
{L("Del"), L("Delete Selected")},
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete Selected")},
|
||||
#endif
|
||||
// Help
|
||||
{ "?", L("Show keyboard shortcuts list") }
|
||||
@@ -231,12 +231,12 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{alt + L("Left mouse button"), L("Select a part")},
|
||||
{ctrl + L("Left mouse button"), L("Select multiple objects")},
|
||||
{shift + L("Left mouse button"), L("Select objects by rectangle")},
|
||||
{L("Arrow Up"), L("Move selection 10mm in positive Y direction")},
|
||||
{L("Arrow Down"), L("Move selection 10mm in negative Y direction")},
|
||||
{L("Arrow Left"), L("Move selection 10mm in negative X direction")},
|
||||
{L("Arrow Right"), L("Move selection 10mm in positive X direction")},
|
||||
{L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Move selection 10mm in positive Y direction")},
|
||||
{L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Move selection 10mm in negative Y direction")},
|
||||
{L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Move selection 10mm in negative X direction")},
|
||||
{L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Move selection 10mm in positive X direction")},
|
||||
{shift + L("Any arrow"), L("Movement step set to 1mm")},
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{"1-9", L("Keyboard 1-9: set filament for object/part")},
|
||||
{ctrl + "0", L("Camera view - Default")},
|
||||
{ctrl + "1", L("Camera view - Top")},
|
||||
@@ -266,14 +266,14 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ "I", L("Zoom in") },
|
||||
{ "O", L("Zoom out") },
|
||||
{ "V", L("Toggle printable for object/part") },
|
||||
{ L("Tab"), L("Switch between Prepare/Preview") },
|
||||
{ L("Space"), L("Open actions speed dial") },
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview") },
|
||||
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open actions speed dial") },
|
||||
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Plater"), "" }, plater_shortcuts });
|
||||
|
||||
Shortcuts gizmos_shortcuts = {
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{shift, L("Move: press to snap by 1mm")},
|
||||
{ctrl + L("Mouse wheel"), L("Support/Color Painting: adjust pen radius")},
|
||||
{alt + L("Mouse wheel"), L("Support/Color Painting: adjust section position")},
|
||||
@@ -282,8 +282,8 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
|
||||
Shortcuts object_list_shortcuts = {
|
||||
{"1-9", L("Set extruder number for the objects and parts") },
|
||||
{L("Del"), L("Delete objects, parts, modifiers")},
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete objects, parts, modifiers")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{ctrl + "C", L("Copy to clipboard")},
|
||||
{ctrl + "V", L("Paste from clipboard")},
|
||||
{ctrl + "X", L("Cut")},
|
||||
@@ -291,26 +291,26 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ctrl + "K", L("Clone Selected")},
|
||||
{ctrl + "Z", L("Undo")},
|
||||
{ctrl + "Y", L("Redo")},
|
||||
{L("Space"), L("Select the object/part and press space to change the name")},
|
||||
{L_CONTEXT("Space", "Keyboard Shortcut"), L("Select the object/part and press space to change the name")},
|
||||
{L("Mouse click"), L("Select the object/part and mouse click to change the name")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts });
|
||||
}
|
||||
|
||||
Shortcuts preview_shortcuts = {
|
||||
{ L("Arrow Up"), L("Vertical slider - Move active thumb Up")},
|
||||
{ L("Arrow Down"), L("Vertical slider - Move active thumb Down")},
|
||||
{ L("Arrow Left"), L("Horizontal slider - Move active thumb Left")},
|
||||
{ L("Arrow Right"), L("Horizontal slider - Move active thumb Right")},
|
||||
{ L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Up")},
|
||||
{ L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Down")},
|
||||
{ L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Left")},
|
||||
{ L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Right")},
|
||||
{ "L", L("On/Off one layer mode of the vertical slider")},
|
||||
{ "C", L("On/Off G-code window")},
|
||||
{ L("Tab"), L("Switch between Prepare/Preview")},
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview")},
|
||||
{shift + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{shift + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ L("Home"), L("Horizontal slider - Move to start position")},
|
||||
{ L("End"), L("Horizontal slider - Move to last position")},
|
||||
{ L_CONTEXT("Home", "Keyboard Shortcut"), L("Horizontal slider - Move to start position")},
|
||||
{ L_CONTEXT("End", "Keyboard Shortcut"), L("Horizontal slider - Move to last position")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Preview"), "" }, preview_shortcuts });
|
||||
}
|
||||
@@ -341,7 +341,10 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const ShortcutsItem& s
|
||||
|
||||
for (int i = 0; i < items_count; ++i) {
|
||||
const auto &[shortcut, description] = shortcuts.second[i];
|
||||
auto key = new wxStaticText(scrollable_panel, wxID_ANY, _(shortcut));
|
||||
// Keyboard keys carry a "Keyboard Shortcut" context so translators keep them in English;
|
||||
// mouse-input labels are ordinary phrases and use the plain lookup.
|
||||
const bool is_mouse = shortcut.find("Mouse") != std::string::npos || shortcut.find("mouse") != std::string::npos;
|
||||
auto key = new wxStaticText(scrollable_panel, wxID_ANY, is_mouse ? _(shortcut) : _L_CONTEXT(shortcut, "Keyboard Shortcut"));
|
||||
key->SetForegroundColour(wxColour(50, 58, 61));
|
||||
key->SetFont(bold_font);
|
||||
grid_sizer->Add(key, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
|
||||
@@ -215,8 +215,8 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve
|
||||
const EOptionType option_type = move_type_to_option(curr_type);
|
||||
if (option_type == EOptionType::COUNT || option_type == EOptionType::Travels || option_type == EOptionType::Wipes) {
|
||||
if (ret.vertices.empty() || prev.type != curr.type || prev.extrusion_role != curr.extrusion_role
|
||||
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
||||
|| prev.mm3_per_mm != curr.mm3_per_mm) {
|
||||
// ORCA: Split the path when a preview value changes.
|
||||
|| prev.mm3_per_mm != curr.mm3_per_mm || prev.acceleration != curr.acceleration || prev.jerk != curr.jerk) {
|
||||
// to allow libvgcode to properly detect the start/end of a path we need to add a 'phantom' vertex
|
||||
// equal to the current one with the exception of the position, which should match the previous move position,
|
||||
// and the times, which are set to zero
|
||||
|
||||
+202
-98
@@ -493,9 +493,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
});
|
||||
|
||||
//BBS
|
||||
Bind(EVT_SELECT_TAB, [this](wxCommandEvent&evt) {
|
||||
TabPosition pos = (TabPosition)evt.GetInt();
|
||||
m_tabpanel->SetSelection(pos);
|
||||
Bind(EVT_SELECT_TAB, [this](wxCommandEvent& evt) {
|
||||
m_tabpanel->SelectPageByName(evt.GetString());
|
||||
});
|
||||
|
||||
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
|
||||
@@ -702,13 +701,13 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
}
|
||||
return;}
|
||||
#endif
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SetSelection(tpPreview); } return; }
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
|
||||
m_plater->apply_background_progress();
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
if (m_print_enable) {
|
||||
if (wxGetApp().preset_bundle->use_bbl_network())
|
||||
if (wxGetApp().preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
|
||||
else
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
||||
@@ -723,7 +722,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;}
|
||||
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
|
||||
if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) {
|
||||
if (m_plater && is_prepare_or_preview_tab()) {
|
||||
m_plater->sidebar().can_search();
|
||||
}
|
||||
}
|
||||
@@ -739,7 +738,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'I') {
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'I' && !evt.ShiftDown()) {
|
||||
if (!can_add_models()) return;
|
||||
if (m_plater) { m_plater->add_file(); }
|
||||
return;
|
||||
@@ -1007,8 +1006,8 @@ void MainFrame::update_layout()
|
||||
m_layout = layout;
|
||||
|
||||
// From the very beginning the Print settings should be selected
|
||||
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1;
|
||||
m_last_selected_tab = 1;
|
||||
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? TAB_ID_HOME : TAB_ID_PREPARE;
|
||||
m_last_selected_tab = TAB_ID_PREPARE;
|
||||
|
||||
// Set new settings
|
||||
switch (m_layout)
|
||||
@@ -1016,14 +1015,18 @@ void MainFrame::update_layout()
|
||||
case ESettingsLayout::Old:
|
||||
{
|
||||
m_plater->Reparent(m_tabpanel);
|
||||
m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
|
||||
m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
|
||||
// Right after Home — or first, when there is no Home tab (PositionAfter() would
|
||||
// append instead, and by now the other built-in tabs are already in place).
|
||||
const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME);
|
||||
const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast<size_t>(home_idx) + 1;
|
||||
m_tabpanel->InsertPage(prepare_pos, TAB_ID_PREPARE, m_plater, _L("Prepare"), "tab_3d_active");
|
||||
m_tabpanel->InsertPage(prepare_pos + 1, TAB_ID_PREVIEW, m_plater, _L("Preview"), "tab_preview_active");
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0);
|
||||
|
||||
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
|
||||
{
|
||||
// jump to 3deditor under preview_only mode
|
||||
if (evt.GetId() == tp3DEditor){
|
||||
if (evt.GetId() == m_tabpanel->FindPageByName(TAB_ID_PREPARE)) {
|
||||
Sidebar& sidebar = GUI::wxGetApp().sidebar();
|
||||
if (sidebar.need_auto_sync_after_connect_printer()) {
|
||||
sidebar.set_need_auto_sync_after_connect_printer(false);
|
||||
@@ -1107,6 +1110,9 @@ void MainFrame::update_edge_panels()
|
||||
void MainFrame::shutdown()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
|
||||
if (m_project != nullptr)
|
||||
m_project->shutdown();
|
||||
m_plugin_pages.shutdown();
|
||||
#ifdef __WXGTK__
|
||||
// Edge panels are child windows — wxWidgets destroys them automatically.
|
||||
m_edge_bottom = nullptr;
|
||||
@@ -1252,15 +1258,14 @@ void MainFrame::init_tabpanel() {
|
||||
#endif
|
||||
//BBS
|
||||
wxWindow* panel = m_tabpanel->GetCurrentPage();
|
||||
int sel = m_tabpanel->GetSelection();
|
||||
//wxString page_text = m_tabpanel->GetPageText(sel);
|
||||
m_last_selected_tab = m_tabpanel->GetSelection();
|
||||
m_last_selected_tab = m_tabpanel->GetSelectedPageName();
|
||||
if (panel == m_plater) {
|
||||
if (sel == tp3DEditor) {
|
||||
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
|
||||
m_param_panel->OnActivate();
|
||||
}
|
||||
else if (sel == tpPreview) {
|
||||
else if (m_last_selected_tab == TAB_ID_PREVIEW) {
|
||||
m_plater->reset_check_status();
|
||||
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
|
||||
return;
|
||||
@@ -1275,7 +1280,7 @@ void MainFrame::init_tabpanel() {
|
||||
//monitor
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
if (sel == tp3DEditor) {
|
||||
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||
m_topbar->EnableUndoRedoItems();
|
||||
}
|
||||
else {
|
||||
@@ -1285,34 +1290,16 @@ void MainFrame::init_tabpanel() {
|
||||
|
||||
if (panel)
|
||||
panel->SetFocus();
|
||||
|
||||
/*switch (sel) {
|
||||
case TabPosition::tpHome:
|
||||
show_option(false);
|
||||
break;
|
||||
case TabPosition::tp3DEditor:
|
||||
show_option(true);
|
||||
break;
|
||||
case TabPosition::tpPreview:
|
||||
show_option(true);
|
||||
break;
|
||||
case TabPosition::tpMonitor:
|
||||
show_option(false);
|
||||
break;
|
||||
default:
|
||||
show_option(false);
|
||||
break;
|
||||
}*/
|
||||
});
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
m_webview = new WebViewPanel(m_tabpanel);
|
||||
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
|
||||
wxString url = evt.GetString();
|
||||
select_tab(MainFrame::tpHome);
|
||||
select_tab(TAB_ID_HOME);
|
||||
m_webview->load_url(url);
|
||||
});
|
||||
m_tabpanel->AddPage(m_webview, "", "tab_home_active", "tab_home_active", false);
|
||||
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
|
||||
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
}
|
||||
|
||||
@@ -1327,7 +1314,7 @@ void MainFrame::init_tabpanel() {
|
||||
//BBS add pages
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
|
||||
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
|
||||
@@ -1342,16 +1329,20 @@ void MainFrame::init_tabpanel() {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
// TODO: change the bitmap
|
||||
m_tabpanel->AddPage(m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"), std::string("tab_multi_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
|
||||
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_project->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_project, _L("Project"), std::string("tab_auxiliary_active"), std::string("tab_auxiliary_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
|
||||
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"), std::string("tab_calibration_active"), false);
|
||||
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
|
||||
|
||||
// Plugin pages are appended after the built-in tabs; their ids are namespaced
|
||||
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
|
||||
m_plugin_pages.initialize(m_tabpanel);
|
||||
|
||||
if (m_plater) {
|
||||
// load initial config
|
||||
@@ -1368,9 +1359,97 @@ void MainFrame::init_tabpanel() {
|
||||
}
|
||||
|
||||
// SoftFever
|
||||
void MainFrame::show_device(bool bBBLPrinter) {
|
||||
void MainFrame::show_device(bool should_use_native) {
|
||||
auto idx = -1;
|
||||
if (bBBLPrinter) {
|
||||
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
|
||||
// The web Device page is the extra tab printer-agents mode shows alongside the native one.
|
||||
// Printers that drive the native Bambu device tab have nothing to put in it, so they don't
|
||||
// get it — otherwise a Bambu user sees two Device tabs, one of them permanently empty.
|
||||
const bool want_web_device_tab = use_printer_agents && wxGetApp().preset_bundle != nullptr &&
|
||||
!wxGetApp().preset_bundle->use_bbl_device_tab();
|
||||
|
||||
// Remove the extra page before switching to any layout that shouldn't have it.
|
||||
if (!want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_printer_agents) {
|
||||
if (!m_monitor) {
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
|
||||
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
|
||||
if (m_printer_view == nullptr) {
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
|
||||
wxString url = evt.GetString();
|
||||
wxString key = evt.GetAPIkey();
|
||||
// select_tab(MainFrame::tpMonitor);
|
||||
m_printer_view->load_url(url, key);
|
||||
});
|
||||
}
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
||||
m_multi_machine->Show(false);
|
||||
// Past the web Device tab when it is already there, so enabling multi-machine
|
||||
// later can't wedge this page between the two Device tabs.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
|
||||
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
|
||||
m_calibration->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
}
|
||||
|
||||
if (want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
// Immediately right of the native Device tab, not at the end of the tab bar.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
|
||||
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
#endif // _MSW_DARK_MODE
|
||||
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_use_native) {
|
||||
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
|
||||
fit_tab_labels(); // ORCA on printer change - same button layout
|
||||
return;
|
||||
@@ -1387,7 +1466,8 @@ void MainFrame::show_device(bool bBBLPrinter) {
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
@@ -1396,18 +1476,18 @@ void MainFrame::show_device(bool bBBLPrinter) {
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
m_multi_machine->Show(false);
|
||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
||||
std::string("tab_multi_active"), false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
|
||||
_L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_calibration->Show(false);
|
||||
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
|
||||
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
||||
std::string("tab_calibration_active"), false);
|
||||
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
|
||||
// append, so its position doesn't depend on the relayout() below running afterwards.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
@@ -1440,10 +1520,17 @@ void MainFrame::show_device(bool bBBLPrinter) {
|
||||
});
|
||||
}
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"));
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
}
|
||||
|
||||
bool MainFrame::is_prepare_or_preview_tab() const
|
||||
{
|
||||
const wxString tab = m_tabpanel->GetSelectedPageName();
|
||||
return tab == TAB_ID_PREPARE || tab == TAB_ID_PREVIEW;
|
||||
}
|
||||
|
||||
void MainFrame::fit_tab_labels()
|
||||
@@ -1475,7 +1562,7 @@ void MainFrame::fit_tab_labels()
|
||||
bool MainFrame::preview_only_hint()
|
||||
{
|
||||
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelection() %tp3DEditor;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelectedPageName() %wxString(TAB_ID_PREPARE);
|
||||
|
||||
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"));
|
||||
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
|
||||
@@ -1793,22 +1880,22 @@ bool MainFrame::can_clone() const {
|
||||
|
||||
bool MainFrame::can_select() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_deselect() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_delete() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_delete_all() const
|
||||
{
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
|
||||
return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
|
||||
}
|
||||
|
||||
bool MainFrame::can_reslice() const
|
||||
@@ -1917,7 +2004,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||
else
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
||||
this->m_tabpanel->SetSelection(tpPreview);
|
||||
this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1999,7 +2086,8 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
SidePopup* p = new SidePopup(this);
|
||||
|
||||
if (wxGetApp().preset_bundle
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()
|
||||
&& !wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// ThirdParty Buttons
|
||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||
export_gcode_btn->SetCornerRadius(0);
|
||||
@@ -2014,10 +2102,10 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
});
|
||||
|
||||
// upload and print
|
||||
SideButton* send_gcode_btn = new SideButton(p, _CTX("Print", "Verb"), "");
|
||||
SideButton* send_gcode_btn = new SideButton(p, _L_CONTEXT("Print", "Verb"), "");
|
||||
send_gcode_btn->SetCornerRadius(0);
|
||||
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_CTX("Print", "Verb"));
|
||||
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
@@ -2132,7 +2220,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
|
||||
const auto preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
if (preset_bundle->use_bbl_network()) {
|
||||
if (preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// BBL network support everything
|
||||
} else {
|
||||
support_send = false; // All 3rd print hosts do not have the send options
|
||||
@@ -2654,18 +2742,18 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame,
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//view_menu->AppendSeparator();
|
||||
//TRN To be shown in the main menu View->Top
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Top") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Top", "Camera View") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//TRN To be shown in the main menu View->Bottom
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Bottom") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Bottom", "Camera View") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Front") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Front", "Camera View") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Rear") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Rear", "Camera View") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX("Right", "Camera View") + "\t" + ctrl + "6", _L("Right View"),[mainFrame](wxCommandEvent &) { mainFrame->select_view("right"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Right", "Camera View") + "\t" + ctrl + "6", _L("Right View"),[mainFrame](wxCommandEvent &) { mainFrame->select_view("right"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
}
|
||||
|
||||
@@ -2856,7 +2944,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
_L("Paste clipboard"), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
|
||||
"menu_paste", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
|
||||
// BBS Delete selected
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L("Del"),
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L_CONTEXT("Del", "Keyboard Shortcut"),
|
||||
_L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); },
|
||||
"menu_remove", nullptr, [this](){return can_delete(); }, this);
|
||||
//BBS: delete all
|
||||
@@ -2941,7 +3029,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
"", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
|
||||
#if 0
|
||||
// BBS Delete selected
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L("Backspace"),
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L_CONTEXT("Backspace", "Keyboard Shortcut"),
|
||||
_L("Deletes the current selection"),[this](wxCommandEvent&) {
|
||||
m_plater->remove_selected();
|
||||
},
|
||||
@@ -2996,7 +3084,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
m_plater->select_all(); },
|
||||
"", nullptr, [this](){return can_select(); }, this);
|
||||
// BBS Deslect All
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Deselect All") + sep + _L("Esc"),
|
||||
append_menu_item(editMenu, wxID_ANY, _L("Deselect All") + sep + _L_CONTEXT("Esc", "Keyboard Shortcut"),
|
||||
_L("Deselects all objects"), [this, handle_key_event](wxCommandEvent&) {
|
||||
wxKeyEvent e;
|
||||
e.SetEventType(wxEVT_KEY_DOWN);
|
||||
@@ -3063,7 +3151,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
this, [this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
|
||||
|
||||
viewMenu->AppendSeparator();
|
||||
@@ -3072,7 +3160,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_gcode_window();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == tpPreview; },
|
||||
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
|
||||
[this]() { return wxGetApp().show_gcode_window(); }, this);
|
||||
|
||||
append_menu_check_item(
|
||||
@@ -3081,7 +3169,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_3d_navigator();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
this, [this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().show_3d_navigator(); }, this);
|
||||
|
||||
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
|
||||
@@ -3089,15 +3177,14 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_plate_gridlines();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
}, this,
|
||||
[this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
|
||||
[this]() { return is_prepare_or_preview_tab(); },
|
||||
[this]() { return wxGetApp().show_plate_gridlines(); }, this);
|
||||
|
||||
append_menu_item(
|
||||
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
|
||||
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
|
||||
[this]() {
|
||||
return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) &&
|
||||
m_plater->is_sidebar_enabled();
|
||||
return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled();
|
||||
},
|
||||
this);
|
||||
|
||||
@@ -3119,7 +3206,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().toggle_show_outline();
|
||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||
},
|
||||
this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor; },
|
||||
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE; },
|
||||
[this]() { return wxGetApp().show_outline(); }, this);
|
||||
|
||||
/*viewMenu->AppendSeparator();
|
||||
@@ -3262,7 +3349,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
#ifndef __APPLE__
|
||||
m_topbar->SetFileMenu(fileMenu);
|
||||
if (editMenu)
|
||||
m_topbar->AddDropDownSubMenu(editMenu, _L("Edit"));
|
||||
m_topbar->AddDropDownSubMenu(editMenu, _L_CONTEXT("Edit", "Menu"));
|
||||
if (viewMenu)
|
||||
m_topbar->AddDropDownSubMenu(viewMenu, _L("View"));
|
||||
//BBS add Preference
|
||||
@@ -3451,7 +3538,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
|
||||
m_menubar->Append(fileMenu, wxString::Format("&%s", _L("File")));
|
||||
if (editMenu)
|
||||
m_menubar->Append(editMenu, wxString::Format("&%s", _L("Edit")));
|
||||
m_menubar->Append(editMenu, wxString::Format("&%s", _L_CONTEXT("Edit", "Menu")));
|
||||
if (viewMenu)
|
||||
m_menubar->Append(viewMenu, wxString::Format("&%s", _L("View")));
|
||||
/*if (publishMenu)
|
||||
@@ -3920,13 +4007,16 @@ void MainFrame::select_tab(wxPanel* panel)
|
||||
wxGetApp().params_dialog()->Popup();
|
||||
return;
|
||||
}
|
||||
// Not panel->GetName(): Prepare and Preview share the single m_plater window, so the
|
||||
// window has no one correct name. The slot -> id lookup is the only correct resolution.
|
||||
int page_idx = m_tabpanel->FindPage(panel);
|
||||
if (page_idx == tp3DEditor && m_tabpanel->GetSelection() == tpPreview)
|
||||
wxString page_name = (page_idx == wxNOT_FOUND) ? wxString() : m_tabpanel->GetPageName(static_cast<size_t>(page_idx));
|
||||
if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)
|
||||
return;
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
|
||||
page_idx++;*/
|
||||
select_tab(size_t(page_idx));
|
||||
select_tab(page_name);
|
||||
}
|
||||
|
||||
//BBS
|
||||
@@ -3934,7 +4024,7 @@ void MainFrame::jump_to_monitor(std::string dev_id)
|
||||
{
|
||||
if(!m_monitor)
|
||||
return;
|
||||
m_tabpanel->SetSelection(tpMonitor);
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
|
||||
if (!dev_id.empty()) {
|
||||
((MonitorPanel*)m_monitor)->select_machine(dev_id);
|
||||
}
|
||||
@@ -3944,26 +4034,26 @@ void MainFrame::jump_to_multipage()
|
||||
{
|
||||
if(!m_multi_machine)
|
||||
return;
|
||||
m_tabpanel->SetSelection(tpMultiDevice);
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
|
||||
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
|
||||
}
|
||||
|
||||
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
void MainFrame::select_tab(const wxString& id/* = wxString()*/)
|
||||
{
|
||||
//bool tabpanel_was_hidden = false;
|
||||
|
||||
// Controls on page are created on active page of active tab now.
|
||||
// We should select/activate tab before its showing to avoid an UI-flickering
|
||||
auto select = [this, tab](bool was_hidden) {
|
||||
// when tab == -1, it means we should show the last selected tab
|
||||
auto select = [this, id](bool was_hidden) {
|
||||
// when id is empty, it means we should show the last selected tab
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
//size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab;
|
||||
size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : tab;
|
||||
wxString new_selection = id.empty() ? m_last_selected_tab : id;
|
||||
|
||||
if (m_tabpanel->GetSelection() != (int)new_selection)
|
||||
m_tabpanel->SetSelection(new_selection);
|
||||
if (m_tabpanel->GetSelectedPageName() != new_selection)
|
||||
m_tabpanel->SelectPageByName(new_selection);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
/*if (wxGetApp().tabs_as_menu()) {
|
||||
if (Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection)))
|
||||
@@ -3972,10 +4062,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
m_plater->get_current_canvas3D()->render();
|
||||
}*/
|
||||
#endif
|
||||
if (tab == MainFrame::tp3DEditor && m_layout == ESettingsLayout::Old)
|
||||
// Intentionally `id`, not `new_selection`: the fallback-to-last-tab path must not
|
||||
// trigger this render even when the last selected tab was Prepare.
|
||||
if (id == TAB_ID_PREPARE && m_layout == ESettingsLayout::Old)
|
||||
m_plater->canvas3D()->render();
|
||||
else if (was_hidden) {
|
||||
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection));
|
||||
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPageByName(new_selection));
|
||||
if (cur_tab)
|
||||
cur_tab->OnActivate();
|
||||
}
|
||||
@@ -3984,10 +4076,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
select(false);
|
||||
}
|
||||
|
||||
void MainFrame::request_select_tab(TabPosition pos)
|
||||
void MainFrame::request_select_tab(const wxString& id)
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
|
||||
evt->SetInt(pos);
|
||||
evt->SetString(id);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
@@ -4064,7 +4156,7 @@ void MainFrame::set_print_button_to_default(PrintSelectType select_type)
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else if (select_type == PrintSelectType::eSendGcode) {
|
||||
m_print_btn->SetLabel(_CTX("Print", "Verb"));
|
||||
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status() && can_send_gcode();
|
||||
@@ -4253,21 +4345,33 @@ void MainFrame::load_printer_url(wxString url, wxString apikey)
|
||||
void MainFrame::load_printer_url()
|
||||
{
|
||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_device_tab() || NetworkAgentFactory::is_current_printer_agent_plugin())
|
||||
if (preset_bundle.use_bbl_device_tab() && !wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
return;
|
||||
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
if (cfg.opt_string("print_host").empty()) {
|
||||
if (auto *device_manager = wxGetApp().getDeviceManager()) {
|
||||
auto *machine = device_manager->get_selected_machine();
|
||||
if (!machine) {
|
||||
auto machines = device_manager->get_my_machine_list();
|
||||
if (machines.size() == 1)
|
||||
machine = machines.begin()->second;
|
||||
}
|
||||
if (machine && !machine->get_dev_ip().empty())
|
||||
cfg.opt_string("print_host") = machine->get_dev_ip();
|
||||
}
|
||||
}
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect))
|
||||
if (cfg.has("printhost_apikey") && host_type != htSimplyPrint)
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
if (!url.empty()) {
|
||||
load_printer_url(url, apikey);
|
||||
}
|
||||
}
|
||||
|
||||
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelection() == TabPosition::tpMonitor; }
|
||||
bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName() == TAB_ID_MONITOR; }
|
||||
|
||||
|
||||
void MainFrame::refresh_plugin_tips()
|
||||
|
||||
@@ -35,6 +35,21 @@
|
||||
#include "PrinterWebView.hpp"
|
||||
#include "calib_dlg.hpp"
|
||||
#include "MultiMachinePage.hpp"
|
||||
#include "slic3r/plugin/host/PluginPages.hpp"
|
||||
|
||||
// Stable identifiers for MainFrame::m_tabpanel's built-in pages. These are
|
||||
// names rather than positional indices so optional pages cannot shift them.
|
||||
#define TAB_ID_HOME "home"
|
||||
#define TAB_ID_PREPARE "prepare"
|
||||
#define TAB_ID_PREVIEW "preview"
|
||||
#define TAB_ID_MONITOR "monitor"
|
||||
// Printer-agents mode shows the legacy web page alongside the native Device tab, so it needs an
|
||||
// id of its own: sharing TAB_ID_MONITOR makes every name lookup resolve to whichever of the two
|
||||
// comes first, which silently defeats PluginPages' selection round-trip across a tab relayout.
|
||||
#define TAB_ID_MONITOR_WEB "monitor_web"
|
||||
#define TAB_ID_MULTI_DEVICE "multi_device"
|
||||
#define TAB_ID_PROJECT "project"
|
||||
#define TAB_ID_CALIBRATION "calibration"
|
||||
|
||||
#define ENABEL_PRINT_ALL 0
|
||||
|
||||
@@ -115,7 +130,7 @@ class MainFrame : public DPIFrame
|
||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||
wxSizer* m_main_sizer{ nullptr };
|
||||
|
||||
size_t m_last_selected_tab;
|
||||
wxString m_last_selected_tab;
|
||||
|
||||
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
|
||||
std::string get_dir_name(const wxString &full_name) const;
|
||||
@@ -214,19 +229,6 @@ public:
|
||||
#ifdef __APPLE__
|
||||
bool get_mac_full_screen() { return m_mac_fullscreen; }
|
||||
#endif
|
||||
//BBS GUI refactor
|
||||
enum TabPosition
|
||||
{
|
||||
tpHome = 0,
|
||||
tp3DEditor = 1,
|
||||
tpPreview = 2,
|
||||
tpMonitor = 3,
|
||||
tpMultiDevice = 4,
|
||||
tpProject = 5,
|
||||
tpCalibration = 6,
|
||||
tpAuxiliary = 7,
|
||||
toDebugTool = 8,
|
||||
};
|
||||
|
||||
//BBS: add slice&&print status update logic
|
||||
enum SlicePrintEventType
|
||||
@@ -326,8 +328,8 @@ public:
|
||||
// When tab == -1, will be selected last selected tab
|
||||
//BBS: GUI refactor
|
||||
void select_tab(wxPanel* panel);
|
||||
void select_tab(size_t tab = size_t(-1));
|
||||
void request_select_tab(TabPosition pos);
|
||||
void select_tab(const wxString& id = wxString());
|
||||
void request_select_tab(const wxString& id);
|
||||
int get_calibration_curr_tab();
|
||||
void select_view(const std::string& direction);
|
||||
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
|
||||
@@ -358,8 +360,11 @@ public:
|
||||
void RunScript(wxString js);
|
||||
|
||||
//SoftFever
|
||||
void show_device(bool bBBLPrinter);
|
||||
void show_device(bool should_use_native);
|
||||
void fit_tab_labels(); // ORCA
|
||||
// True while either of the two tabs backed by m_plater is selected.
|
||||
bool is_prepare_or_preview_tab() const;
|
||||
PluginPages& plugin_pages() { return m_plugin_pages; }
|
||||
|
||||
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
|
||||
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
|
||||
@@ -385,6 +390,7 @@ public:
|
||||
CalibrationPanel* m_calibration{ nullptr };
|
||||
WebViewPanel* m_webview { nullptr };
|
||||
PrinterWebView* m_printer_view{nullptr};
|
||||
PluginPages m_plugin_pages;
|
||||
wxLogWindow* m_log_window { nullptr };
|
||||
// BBS
|
||||
//wxBookCtrlBase* m_tabpanel { nullptr };
|
||||
|
||||
@@ -556,7 +556,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
} else if (action == 1) {
|
||||
if (fs->GetFileType() == PrinterFileSystem::F_MODEL) {
|
||||
if (index != -1) {
|
||||
auto dlg = new MediaProgressDialog(_CTX("Print", "Verb"), this, [fs] { fs->FetchModelCancel(); });
|
||||
auto dlg = new MediaProgressDialog(_L_CONTEXT("Print", "Verb"), this, [fs] { fs->FetchModelCancel(); });
|
||||
dlg->Update(0, _L("Fetching model information..."));
|
||||
fs->FetchModel(index, [this, fs, dlg, index](int result, std::string const &data) {
|
||||
dlg->Destroy();
|
||||
@@ -566,7 +566,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
wxString msg = data.empty() ? _L("Failed to fetch model information from printer.") :
|
||||
from_u8(data);
|
||||
CallAfter([this, msg] {
|
||||
MessageDialog(this, msg, _CTX("Print", "Verb"), wxOK).ShowModal();
|
||||
MessageDialog(this, msg, _L_CONTEXT("Print", "Verb"), wxOK).ShowModal();
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -579,7 +579,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
|| plate_data_list.empty()) {
|
||||
MessageDialog(this,
|
||||
_L("Failed to parse model information."),
|
||||
_CTX("Print", "Verb"), wxOK).ShowModal();
|
||||
_L_CONTEXT("Print", "Verb"), wxOK).ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -186,17 +186,17 @@ void MonitorPanel::init_tabpanel()
|
||||
|
||||
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
|
||||
m_status_info_panel = new StatusPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), "", true);
|
||||
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
|
||||
|
||||
m_media_file_panel = new MediaFilePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), "", false);
|
||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), "", false);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
|
||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
|
||||
|
||||
m_upgrade_panel = new UpgradePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _CTX(L_CONTEXT("Update", "Firmware"), "Firmware"), "", false);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
|
||||
|
||||
m_hms_panel = new HMSPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), "", false);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
|
||||
|
||||
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||
if (!network_ver.empty()) {
|
||||
@@ -413,7 +413,10 @@ void MonitorPanel::update_hms_tag()
|
||||
bool MonitorPanel::Show(bool show)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
|
||||
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
|
||||
if (wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
#endif
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
|
||||
+112
-24
@@ -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"
|
||||
@@ -65,7 +70,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
||||
main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND | wxTOP, FromDIP(10));
|
||||
|
||||
apply_style(style);
|
||||
SetSizerAndFit(main_sizer);
|
||||
SetSizer(main_sizer);
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
@@ -221,6 +226,7 @@ void MsgDialog::apply_style(long style)
|
||||
|
||||
void MsgDialog::finalize()
|
||||
{
|
||||
GetSizer()->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
@@ -228,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)
|
||||
{
|
||||
@@ -242,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) {
|
||||
@@ -299,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();
|
||||
@@ -336,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>";
|
||||
@@ -359,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);
|
||||
|
||||
@@ -547,7 +636,7 @@ DeleteConfirmDialog::DeleteConfirmDialog(wxWindow *parent, const wxString &title
|
||||
m_del_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
|
||||
m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
|
||||
|
||||
SetSizer(m_main_sizer);
|
||||
SetSizerAndFit(m_main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -582,7 +671,7 @@ Newer3mfVersionDialog::Newer3mfVersionDialog(wxWindow *parent, const Semver *fil
|
||||
main_sizer->Add(content_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
|
||||
main_sizer->Add(get_btn_sizer(), 0, wxEXPAND | wxALL, FromDIP(5));
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -632,7 +721,7 @@ wxBoxSizer *Newer3mfVersionDialog::get_btn_sizer()
|
||||
|
||||
bool file_version_newer = (*m_file_version) > (*m_cloud_version);
|
||||
if (!file_version_newer) {
|
||||
m_update_btn = new Button(this, _CTX(L_CONTEXT("Update", "Software"), "Software"));
|
||||
m_update_btn = new Button(this, _L_CONTEXT(L_CONTEXT("Update", "Software"), "Software"));
|
||||
m_update_btn->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
horizontal_sizer->Add(m_update_btn, 0, wxRIGHT, FromDIP(ButtonProps::ChoiceButtonGap()));
|
||||
|
||||
@@ -745,9 +834,8 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
|
||||
sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15);
|
||||
sizer_main->Add(0, 0, 0, wxTOP, 18);
|
||||
|
||||
SetSizer(sizer_main);
|
||||
SetSizerAndFit(sizer_main);
|
||||
Layout();
|
||||
sizer_main->Fit(this);
|
||||
Centre(wxBOTH);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -86,9 +86,9 @@ void MultiMachinePage::init_tabpanel()
|
||||
m_cloud_task_manager = new CloudTaskManagerPage(m_tabpanel);
|
||||
m_machine_manager = new MultiMachineManagerPage(m_tabpanel);
|
||||
|
||||
m_tabpanel->AddPage(m_machine_manager, _L("Device"), "", true);
|
||||
m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), "", false);
|
||||
m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), "", false);
|
||||
m_tabpanel->AddPage(m_machine_manager, _L("Device"), true);
|
||||
m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), false);
|
||||
m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), false);
|
||||
}
|
||||
|
||||
void MultiMachinePage::init_timer()
|
||||
|
||||
@@ -47,6 +47,7 @@ NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode
|
||||
} else {
|
||||
create_missing_plugin_ui();
|
||||
}
|
||||
main_sizer->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
|
||||
@@ -47,7 +47,7 @@ NetworkTestDialog::NetworkTestDialog(wxWindow* parent, wxWindowID id, const wxSt
|
||||
|
||||
init_bind();
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
@@ -120,11 +120,11 @@ void ButtonsListCtrl::Rescale()
|
||||
|
||||
void ButtonsListCtrl::SetSelection(int sel)
|
||||
{
|
||||
if (m_selection == sel)
|
||||
if (m_selection == sel && sel >= 0 && sel < static_cast<int>(m_pageButtons.size()))
|
||||
return;
|
||||
// BBS: change button color
|
||||
wxColour selected_btn_bg("#009688"); // Gradient #009688
|
||||
if (m_selection >= 0) {
|
||||
if (m_selection >= 0 && m_selection < static_cast<int>(m_pageButtons.size())) {
|
||||
StateColor bg_color = StateColor(
|
||||
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
|
||||
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
|
||||
@@ -132,9 +132,15 @@ void ButtonsListCtrl::SetSelection(int sel)
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal}
|
||||
);
|
||||
m_pageButtons[m_selection]->SetSelected(false);
|
||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||
}
|
||||
|
||||
if (sel < 0 || sel >= static_cast<int>(m_pageButtons.size())) {
|
||||
m_selection = -1;
|
||||
Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
m_selection = sel;
|
||||
|
||||
StateColor bg_color = StateColor(
|
||||
@@ -145,17 +151,19 @@ void ButtonsListCtrl::SetSelection(int sel)
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254, 254, 254), (int) StateColor::Normal}
|
||||
);
|
||||
m_pageButtons[m_selection]->SetSelected(true);
|
||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const std::string &inactive_bmp_name)
|
||||
bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const wxBitmap &bmp /* = wxNullBitmap */)
|
||||
{
|
||||
Button * btn = new Button(this, text.empty() ? text : " " + text, bmp_name, wxNO_BORDER);
|
||||
btn->SetCornerRadius(0);
|
||||
|
||||
if (bmp_name.empty() && bmp.IsOk())
|
||||
btn->SetIcon(bmp);
|
||||
|
||||
int em = em_unit(this);
|
||||
//BBS set size for button
|
||||
btn->SetMinSize({(text.empty() ? 40 : 136) * em / 10, 36 * em / 10});
|
||||
@@ -168,8 +176,6 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
|
||||
StateColor text_color = StateColor(
|
||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal});
|
||||
btn->SetTextColor(text_color);
|
||||
btn->SetInactiveIcon(inactive_bmp_name);
|
||||
btn->SetSelected(false);
|
||||
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
|
||||
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
|
||||
auto sel = it - m_pageButtons.begin();
|
||||
@@ -192,6 +198,14 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
|
||||
|
||||
void ButtonsListCtrl::RemovePage(size_t n)
|
||||
{
|
||||
if (n >= m_pageButtons.size())
|
||||
return;
|
||||
|
||||
if (m_selection == static_cast<int>(n))
|
||||
m_selection = -1;
|
||||
else if (m_selection > static_cast<int>(n))
|
||||
--m_selection;
|
||||
|
||||
Button* btn = m_pageButtons[n];
|
||||
m_pageButtons.erase(m_pageButtons.begin() + n);
|
||||
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
|
||||
@@ -240,6 +254,24 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
|
||||
return btn->GetLabel();
|
||||
}
|
||||
|
||||
// ORCA
|
||||
void ButtonsListCtrl::SetOverflowButton(wxWindow* button)
|
||||
{
|
||||
if (m_overflow_button == button)
|
||||
return;
|
||||
|
||||
if (m_overflow_button != nullptr)
|
||||
m_sizer->Detach(m_overflow_button);
|
||||
|
||||
m_overflow_button = button;
|
||||
|
||||
if (m_overflow_button != nullptr)
|
||||
// Right after the tab buttons (index 0), ahead of any stretch spacer / side_tools.
|
||||
m_sizer->Insert(1, m_overflow_button, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
|
||||
|
||||
m_sizer->Layout();
|
||||
}
|
||||
|
||||
//#endif // _WIN32
|
||||
|
||||
void Notebook::Init()
|
||||
@@ -253,6 +285,8 @@ void Notebook::Init()
|
||||
|
||||
m_showTimeout = m_hideTimeout = 0;
|
||||
|
||||
m_pageNames.clear();
|
||||
|
||||
/* On Linux, Gstreamer wxMediaCtrl does not seem to get along well with
|
||||
* 32-bit X11 visuals (the overlay does not work). Is this a wxWindows
|
||||
* bug? Is this a Gstreamer bug? No idea, but it is our problem ...
|
||||
|
||||
+106
-33
@@ -3,7 +3,11 @@
|
||||
|
||||
//#ifdef _WIN32
|
||||
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
class ScalableButton;
|
||||
@@ -23,13 +27,16 @@ public:
|
||||
void SetSelection(int sel);
|
||||
void UpdateMode();
|
||||
void Rescale();
|
||||
bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const std::string &inactive_bmp_name = "");
|
||||
bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const wxBitmap &bmp = wxNullBitmap);
|
||||
void RemovePage(size_t n);
|
||||
bool SetPageImage(size_t n, const std::string& bmp_name) const;
|
||||
void SetPageText(size_t n, const wxString& strText);
|
||||
void SetCompact(size_t n, bool compact); // ORCA
|
||||
wxString GetPageText(size_t n) const;
|
||||
wxFlexGridSizer* GetBtnsSizer(){return m_buttons_sizer;}; // ORCA
|
||||
// ORCA: a companion widget shown right after the tab buttons (before any side_tools), e.g.
|
||||
// an overflow indicator. Pass nullptr to remove it; ownership stays with the caller.
|
||||
void SetOverflowButton(wxWindow* button);
|
||||
|
||||
private:
|
||||
wxFlexGridSizer* m_buttons_sizer;
|
||||
@@ -40,9 +47,10 @@ private:
|
||||
int m_btn_margin;
|
||||
int m_line_margin;
|
||||
std::vector<wxString> m_pageLabels; // ORCA
|
||||
wxWindow* m_overflow_button{nullptr}; // ORCA
|
||||
};
|
||||
|
||||
class Notebook: public wxBookCtrlBase
|
||||
class Notebook : public wxBookCtrlBase
|
||||
{
|
||||
public:
|
||||
Notebook(wxWindow * parent,
|
||||
@@ -103,7 +111,7 @@ public:
|
||||
// by this control) and show it immediately.
|
||||
bool ShowNewPage(wxWindow * page)
|
||||
{
|
||||
return AddPage(page, wxString(), "", "");
|
||||
return AddPage(page, wxString(), false, NO_IMAGE);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,51 +143,56 @@ public:
|
||||
|
||||
// Implement base class pure virtual methods.
|
||||
|
||||
// adds a new page to the control
|
||||
bool AddPage(wxWindow* page,
|
||||
// Page management. Every insertion funnels through the InsertPage() below; `id` is the
|
||||
// stable page name FindPageByName() resolves. Built-in tabs name a resource bitmap,
|
||||
// plugin pages hand over a ready wxBitmap; wx's own imageId overloads carry neither.
|
||||
bool AddPage(const wxString& id,
|
||||
wxWindow* page,
|
||||
const wxString& text,
|
||||
const std::string& bmp_name,
|
||||
const std::string& inactive_bmp_name,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false)
|
||||
{
|
||||
DoInvalidateBestSize();
|
||||
return InsertPage(GetPageCount(), page, text, bmp_name, inactive_bmp_name, bSelect);
|
||||
return InsertPage(GetPageCount(), id, page, text, bmp_name, bSelect);
|
||||
}
|
||||
|
||||
// Page management
|
||||
virtual bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
bool bSelect = false,
|
||||
int imageId = NO_IMAGE) override
|
||||
bool AddPage(wxWindow* page, const wxString& text, bool bSelect = false, int imageId = NO_IMAGE) override
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
|
||||
DoInvalidateBestSize();
|
||||
return InsertPage(GetPageCount(), page, text, bSelect, imageId);
|
||||
}
|
||||
|
||||
bool InsertPage(size_t n,
|
||||
const wxString& id,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false,
|
||||
const wxBitmap& bmp = wxNullBitmap)
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect);
|
||||
m_pageNames.insert(m_pageNames.begin() + n, id);
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, bmp);
|
||||
|
||||
// wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the new
|
||||
// page to the current page's rect — it never touches visibility, and a freshly
|
||||
// constructed page defaults to shown. Without this it renders on top of whatever
|
||||
// page is currently selected until the next SetSelection() call hides it.
|
||||
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||
page->Hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
const std::string& inactive_bmp_name = "",
|
||||
bool bSelect = false)
|
||||
virtual bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
bool bSelect = false,
|
||||
int WXUNUSED(imageId) = NO_IMAGE) override
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, inactive_bmp_name);
|
||||
|
||||
if (bSelect)
|
||||
SetSelection(n);
|
||||
|
||||
return true;
|
||||
return InsertPage(n, wxString(), page, text, "", bSelect);
|
||||
}
|
||||
|
||||
virtual int SetSelection(size_t n) override
|
||||
@@ -211,8 +224,8 @@ public:
|
||||
return DoSetSelection(n);
|
||||
}
|
||||
|
||||
// Neither labels nor images are supported but we still store the labels
|
||||
// just in case the user code attaches some importance to them.
|
||||
// Labels are stored by the custom button list; wx's image-list API is unused — tab icons
|
||||
// are set directly on the buttons, either from a resource name or a ready wxBitmap.
|
||||
virtual bool SetPageText(size_t n, const wxString & strText) override
|
||||
{
|
||||
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
|
||||
@@ -251,7 +264,64 @@ public:
|
||||
page->SetFocus();
|
||||
}
|
||||
|
||||
// The base clears its page list directly instead of calling DoRemovePage() per page,
|
||||
// which would leave m_pageNames behind. No caller today; kept in sync regardless.
|
||||
virtual bool DeleteAllPages() override
|
||||
{
|
||||
m_pageNames.clear();
|
||||
return wxBookCtrlBase::DeleteAllPages();
|
||||
}
|
||||
|
||||
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
|
||||
void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); }
|
||||
|
||||
// Insertion index just past the first of `ids` that is present, or the end of the bar
|
||||
// if none is — lets call sites state tab order as "after X" instead of re-deriving it.
|
||||
size_t PositionAfter(std::initializer_list<const char*> ids) const
|
||||
{
|
||||
for (const char* id : ids)
|
||||
if (const int idx = FindPageByName(id); idx != wxNOT_FOUND)
|
||||
return static_cast<size_t>(idx) + 1;
|
||||
return GetPageCount();
|
||||
}
|
||||
|
||||
int FindPageByName(const wxString& id) const
|
||||
{
|
||||
if (id.empty())
|
||||
return wxNOT_FOUND;
|
||||
for (size_t i = 0; i < m_pageNames.size(); ++i)
|
||||
if (m_pageNames[i] == id)
|
||||
return static_cast<int>(i);
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
wxWindow* GetPageByName(const wxString& id) const
|
||||
{
|
||||
const int idx = FindPageByName(id);
|
||||
return idx == wxNOT_FOUND ? nullptr : GetPage(static_cast<size_t>(idx));
|
||||
}
|
||||
|
||||
bool SelectPageByName(const wxString& id)
|
||||
{
|
||||
const int idx = FindPageByName(id);
|
||||
if (idx == wxNOT_FOUND)
|
||||
return false;
|
||||
SetSelection(static_cast<size_t>(idx));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inverse of FindPageByName: index -> id. Empty string for an out-of-range
|
||||
// index or a page that was never given an id (e.g. settings Tab pages).
|
||||
wxString GetPageName(size_t n) const
|
||||
{
|
||||
return n < m_pageNames.size() ? m_pageNames[n] : wxString();
|
||||
}
|
||||
|
||||
wxString GetSelectedPageName() const
|
||||
{
|
||||
const int sel = GetSelection();
|
||||
return sel < 0 ? wxString() : GetPageName(static_cast<size_t>(sel));
|
||||
}
|
||||
|
||||
void UpdateMode()
|
||||
{
|
||||
@@ -369,6 +439,7 @@ protected:
|
||||
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
|
||||
if (win)
|
||||
{
|
||||
m_pageNames.erase(m_pageNames.begin() + page);
|
||||
GetBtnsListCtrl()->RemovePage(page);
|
||||
DoSetSelectionAfterRemoval(page);
|
||||
}
|
||||
@@ -394,6 +465,8 @@ protected:
|
||||
private:
|
||||
void Init();
|
||||
|
||||
std::vector<wxString> m_pageNames; // index-parallel to wxBookCtrlBase::m_pages
|
||||
|
||||
wxShowEffect m_showEffect,
|
||||
m_hideEffect;
|
||||
|
||||
|
||||
@@ -1918,7 +1918,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
||||
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
|
||||
}
|
||||
else {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -1985,7 +1985,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
||||
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
|
||||
}
|
||||
else {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -2015,7 +2015,7 @@ void NotificationManager::push_slicing_error_notification(const std::string &tex
|
||||
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||
}
|
||||
if (!ovs.empty()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items(ovs);
|
||||
}
|
||||
return false;
|
||||
@@ -2046,7 +2046,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t
|
||||
auto& objects = wxGetApp().model().objects;
|
||||
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
|
||||
if (iter != objects.end()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
|
||||
}
|
||||
return false;
|
||||
@@ -2338,7 +2338,7 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
|
||||
{
|
||||
float hyper_y = starting_y + m_endlines.size() * shift_y + m_line_height * .5f;
|
||||
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height * .5f;
|
||||
std::string dont_show_text = _u8L("Don't show again") + std::to_string(m_endlines.size());
|
||||
std::string dont_show_text = _u8L("Don't show again");
|
||||
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
|
||||
|
||||
if (!m_multiline && m_lines_count > 2) {
|
||||
@@ -2693,7 +2693,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
|
||||
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||
}
|
||||
if (!ovs.empty()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items(ovs);
|
||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
||||
}
|
||||
@@ -2777,7 +2777,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (!sel_items.empty()) {
|
||||
obj_list->select_items(sel_items);
|
||||
|
||||
@@ -230,7 +230,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
|
||||
// add label if any
|
||||
if (is_multioption_line && !option.label.empty()) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
auto label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ? _CTX(option.label, "Layers") :
|
||||
auto label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ? _L_CONTEXT(option.label, "Layers") :
|
||||
_(option.label);
|
||||
// BBS
|
||||
// label += ":";
|
||||
@@ -882,7 +882,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord h_pos, wxCoord v_pos)
|
||||
if (is_multioption_line && !option.label.empty()) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_CTX(option.label, "Layers") : _(option.label);
|
||||
_L_CONTEXT(option.label, "Layers") : _(option.label);
|
||||
//if (!ctrl->opt_group->option_label_at_right) // BBS
|
||||
//label += ":";
|
||||
|
||||
|
||||
@@ -54,6 +54,9 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
||||
case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::plugin_picker: m_fields.emplace(id, PluginField::Create<PluginField>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::plugin_config: m_fields.emplace(id, PluginConfigField::Create<PluginConfigField>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::printer_agent_select: m_fields.emplace(
|
||||
id, PrinterAgentChoice::Create<PrinterAgentChoice>(this->ctrl_parent(), opt, id));
|
||||
break;
|
||||
default:
|
||||
switch (opt.type) {
|
||||
case coFloatOrPercent:
|
||||
@@ -386,6 +389,7 @@ void OptionsGroup::activate_line(Line& line)
|
||||
}
|
||||
if (label != nullptr && line.label_tooltip != "")
|
||||
label->SetToolTip(line.label_tooltip);
|
||||
line.label_widget = label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +436,7 @@ void OptionsGroup::activate_line(Line& line)
|
||||
if ((is_multioption_line || line.label.IsEmpty()) && !option.label.empty() && !custom_ctrl) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
wxString str_label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_CTX(option.label, "Layers") :
|
||||
_L_CONTEXT(option.label, "Layers") :
|
||||
_(option.label);
|
||||
label = new wxStaticText(this->ctrl_parent(), wxID_ANY, str_label + ": ", wxDefaultPosition, // wxDefaultSize);
|
||||
wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT);
|
||||
@@ -574,6 +578,7 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
|
||||
for (Line& line : m_lines) {
|
||||
if (line.near_label_widget_win)
|
||||
line.near_label_widget_win = nullptr;
|
||||
line.label_widget = nullptr;
|
||||
|
||||
if (line.widget_sizer) {
|
||||
line.widget_sizer->Clear(true);
|
||||
@@ -652,6 +657,16 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index
|
||||
|
||||
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
|
||||
{
|
||||
if (opt_id == "printer_agent") {
|
||||
// TODO: Replace this option-specific branch with a generic value adapter if
|
||||
// more fields need custom field-value to config-value conversion.
|
||||
if (const std::string* id = boost::any_cast<std::string>(&value))
|
||||
this->change_opt_value("printer_agent", wxGetApp().canonical_printer_agent_id(*id));
|
||||
|
||||
OptionsGroup::on_change_OG(opt_id, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_opt_map.empty()) {
|
||||
auto it = m_opt_map.find(opt_id);
|
||||
if (it == m_opt_map.end()) {
|
||||
@@ -770,6 +785,19 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (opt_key == "printer_agent")
|
||||
{
|
||||
// why: printer_agent is a coString kept out of m_opt_map. The generic non-opt_map revert
|
||||
// below restores the edited config from get_value(), but a deregistered/"(missing)" saved
|
||||
// id has no selectable row, so the field yields no value and the edited config keeps the
|
||||
// user's interim pick -> stuck dirty. Restore the SAVED id straight into the edited config
|
||||
// (displayable or not; config is the saved or system baseline), then repaint and notify.
|
||||
const std::string saved_id = config.opt_string("printer_agent");
|
||||
set_value(opt_key, saved_id);
|
||||
this->change_opt_value(opt_key, saved_id);
|
||||
OptionsGroup::on_change_OG(opt_key, saved_id);
|
||||
return;
|
||||
}
|
||||
else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
|
||||
// This option don't have corresponded field
|
||||
opt_key == "printable_area" || opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "thumbnails" ||
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
widget_t widget {nullptr};
|
||||
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
|
||||
wxWindow* near_label_widget_win {nullptr};
|
||||
wxStaticText* label_widget {nullptr};
|
||||
wxSizer* widget_sizer {nullptr};
|
||||
wxSizer* extra_widget_sizer {nullptr};
|
||||
//BBS: export the extra colume widget
|
||||
@@ -81,6 +82,14 @@ public:
|
||||
label(_(label)), label_tooltip(_(tooltip)) {}
|
||||
Line() : m_is_separator(true) {}
|
||||
|
||||
void set_label(const wxString& new_label) {
|
||||
label = new_label;
|
||||
if (label_widget != nullptr) {
|
||||
label_widget->SetLabel(label + (label.IsEmpty() ? "" : ": "));
|
||||
label_widget->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_separator() const { return m_is_separator; }
|
||||
bool has_only_option(const std::string& opt_key) const { return m_options.size() == 1 && m_options[0].opt_id == opt_key; }
|
||||
|
||||
|
||||
@@ -2262,6 +2262,12 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
|
||||
}
|
||||
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
|
||||
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
|
||||
// Read from the passed plate config — m_print may not have been applied yet
|
||||
// (fresh plates, CLI), in which case its PrintConfig still holds defaults.
|
||||
const auto *purge_opt = config.option<ConfigOptionBool>("purge_in_prime_tower");
|
||||
const auto *semm_opt = config.option<ConfigOptionBool>("single_extruder_multi_material");
|
||||
const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value;
|
||||
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size);
|
||||
if (use_rib_wall) {
|
||||
depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||
if (need_wipe_tower || plate_extruder_size > 1) {
|
||||
@@ -2274,7 +2280,9 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
|
||||
}
|
||||
}
|
||||
else {
|
||||
depth = volume/ (layer_height * w) *extra_spacing;
|
||||
depth = volume / (layer_height * w);
|
||||
// The flush volumes already hold the spacing between wipes.
|
||||
if (!semm_flush) depth *= extra_spacing;
|
||||
if (need_wipe_tower || depth > EPSILON) {
|
||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||
depth = std::max((double)min_wipe_tower_depth, depth);
|
||||
@@ -2298,6 +2306,7 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
||||
int plate_width=m_width, plate_depth=m_depth;
|
||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
||||
float depth = wt_size(1);
|
||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
||||
@@ -2662,7 +2671,8 @@ bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* boundi
|
||||
|
||||
if (instance_box.min.z() < SINKING_Z_THRESHOLD) {
|
||||
// Orca: For sinking object, we use a more expensive algorithm so part below build plate won't be considered
|
||||
if (plate_box.intersects(instance_box)) {
|
||||
// m_plater is null in CLI mode.
|
||||
if (m_plater && plate_box.intersects(instance_box)) {
|
||||
// TODO: FIXME: this does not take exclusion area into account
|
||||
const BuildVolume build_volume(get_shape(), m_plater->build_volume().printable_height(), m_extruder_areas, m_extruder_heights);
|
||||
const auto state = instance->calc_print_volume_state(build_volume);
|
||||
@@ -3335,6 +3345,11 @@ BoundingBoxf3 PartPlate::get_build_volume(bool use_share)
|
||||
return plate_box;
|
||||
}
|
||||
|
||||
Polygon PartPlate::get_shared_printable_polygon() const
|
||||
{
|
||||
return m_extruder_areas.empty() ? Polygon::new_scale(m_shape) : get_shared_poly(m_extruder_areas);
|
||||
}
|
||||
|
||||
bool PartPlate::contains(const Vec3d& point) const
|
||||
{
|
||||
return m_bounding_box.contains(point);
|
||||
@@ -4373,22 +4388,21 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
||||
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
||||
}
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
|
||||
float w = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_tower_width"))->value;
|
||||
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
|
||||
bool enable_wrapping = false;
|
||||
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
|
||||
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
|
||||
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
|
||||
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
|
||||
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
|
||||
|
||||
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
|
||||
wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 2, false, enable_wrapping);
|
||||
wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping);
|
||||
}
|
||||
|
||||
// Compute brim-aware margin: brim extends outward from tower position
|
||||
float brim_width = 0.f;
|
||||
const ConfigOptionFloat *brim_opt = print_cfg.option<ConfigOptionFloat>("prime_tower_brim_width");
|
||||
const ConfigOptionFloat *brim_opt = full_config.option<ConfigOptionFloat>("prime_tower_brim_width");
|
||||
if (brim_opt) {
|
||||
brim_width = brim_opt->value;
|
||||
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
|
||||
@@ -4410,6 +4424,18 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
||||
}
|
||||
}
|
||||
|
||||
// The bounding box above still allows a corner a delta or hexagonal bed does not have, and the
|
||||
// prime tower is validated against the real outline — pull it onto the bed before storing.
|
||||
{
|
||||
Polygons bed{part_plate->get_shared_printable_polygon()};
|
||||
bed.front().translate(Point(-scaled(plate_origin.x()), -scaled(plate_origin.y()))); // into the frame x/y live in
|
||||
const BoundingBox tower(Point::new_scale(x, y),
|
||||
Point::new_scale(x + wipe_tower_size(0), y + wipe_tower_size(1)));
|
||||
const Vec2f move = WipeTower::move_box_inside_polygon(tower, bed, scaled<coord_t>(margin));
|
||||
x += move.x();
|
||||
y += move.y();
|
||||
}
|
||||
|
||||
ConfigOptionFloat wt_x_opt(x);
|
||||
ConfigOptionFloat wt_y_opt(y);
|
||||
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0);
|
||||
@@ -4551,6 +4577,9 @@ int PartPlateList::create_plate(bool adjust_position)
|
||||
return -1;
|
||||
int cols = compute_colum_count(new_index + 1);
|
||||
int old_cols = compute_colum_count(new_index);
|
||||
// Orca: Rebuild plate membership before moving instances during a grid reflow.
|
||||
if (adjust_position && old_cols != cols)
|
||||
reload_all_objects();
|
||||
|
||||
origin = compute_origin(new_index, cols);
|
||||
plate = new PartPlate(this, origin, m_plate_width, m_plate_depth, m_plate_height, m_plater, m_model, true, printer_technology);
|
||||
@@ -5040,6 +5069,9 @@ int PartPlateList::move_plate_to_index(int old_index, int new_index)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Orca: Rebuild plate membership before moving the plates.
|
||||
reload_all_objects();
|
||||
|
||||
if (old_index < new_index)
|
||||
{
|
||||
delta = 1;
|
||||
|
||||
@@ -425,6 +425,9 @@ public:
|
||||
const BoundingBox get_bounding_box_crd();
|
||||
BoundingBoxf3 get_plate_box() {return get_build_volume();}
|
||||
BoundingBoxf3 get_build_volume(bool use_share = false);
|
||||
// Polygon counterpart of get_build_volume(true), in scaled world coordinates. The bounding box
|
||||
// that one returns hides the corners a non-rectangular bed does not have.
|
||||
Polygon get_shared_printable_polygon() const;
|
||||
|
||||
const std::vector<BoundingBoxf3>& get_exclude_areas() { return m_exclude_bounding_box; }
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_simplebook->AddPage(m_book_third_panel, _("dialog page"), false);
|
||||
m_sizer->Add(m_simplebook, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
SetSizer(m_sizer);
|
||||
SetSizerAndFit(m_sizer);
|
||||
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
|
||||
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
|
||||
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
|
||||
@@ -281,7 +281,6 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "format.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -62,7 +61,7 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent) :
|
||||
Tab *tab = wxGetApp().get_tab(Preset::TYPE_PRINTER);
|
||||
m_presets = tab->get_presets();
|
||||
const Preset &sel_preset = m_presets->get_selected_preset();
|
||||
std::string suffix = _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName");
|
||||
std::string suffix = _u8L_CONTEXT(L_CONTEXT("Copy", "PresetName"), "PresetName");
|
||||
std::string preset_name = sel_preset.is_default ? "Untitled" : sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : sel_preset.name;
|
||||
|
||||
auto input_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -128,22 +127,8 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog()
|
||||
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
|
||||
{
|
||||
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
// Special handling for printer_agent: convert fake enum index to string agent ID
|
||||
if (opt_key == "printer_agent") {
|
||||
try {
|
||||
int selected_idx = boost::any_cast<int>(value);
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
if (selected_idx >= 0 && selected_idx < static_cast<int>(agents.size())) {
|
||||
m_config->set_key_value("printer_agent",
|
||||
new ConfigOptionString(agents[selected_idx].id));
|
||||
}
|
||||
} catch (const boost::bad_any_cast&) {
|
||||
// If value is not an int, ignore
|
||||
}
|
||||
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
|
||||
this->update();
|
||||
} else if (opt_key == "host_type" || opt_key == "printhost_authorization_type") {
|
||||
this->update();
|
||||
}
|
||||
if (opt_key == "print_host")
|
||||
this->update_printhost_buttons();
|
||||
if (opt_key == "printhost_port")
|
||||
@@ -154,47 +139,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
|
||||
m_optgroup->append_single_option_line("host_type");
|
||||
|
||||
// Build printer agent dropdown from registry (only if network agent is available)
|
||||
if (wxGetApp().getAgent() != nullptr) {
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
|
||||
if (!agents.empty()) {
|
||||
// Create a fake enum option to force a Choice widget instead of TextCtrl
|
||||
// (printer_agent is coString in config, but we need a dropdown)
|
||||
ConfigOptionDef def;
|
||||
def.type = coEnum;
|
||||
def.width = Field::def_width_wider();
|
||||
def.label = L("Printer Agent");
|
||||
def.tooltip = L("Select the network agent implementation for printer communication. "
|
||||
"Available agents are registered at startup.");
|
||||
def.mode = comAdvanced;
|
||||
|
||||
// Populate enum values and labels from registered agents
|
||||
for (const auto& agent : agents) {
|
||||
def.enum_values.push_back(agent.id);
|
||||
def.enum_labels.push_back(agent.display_name);
|
||||
}
|
||||
|
||||
// Resolve selected agent: use config value if valid, otherwise fall back to default
|
||||
std::string selected_agent = m_config->opt_string("printer_agent");
|
||||
auto it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
if (it == agents.end()) {
|
||||
selected_agent = ORCA_PRINTER_AGENT_ID;
|
||||
it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
}
|
||||
|
||||
if (it != agents.end()) {
|
||||
size_t default_idx = std::distance(agents.begin(), it);
|
||||
def.set_default_value(new ConfigOptionInt(static_cast<int>(default_idx)));
|
||||
}
|
||||
|
||||
// Create and append the option line
|
||||
auto agent_option = Option(def, "printer_agent");
|
||||
Line agent_line = m_optgroup->create_single_option_line(agent_option);
|
||||
m_optgroup->append_line(agent_line);
|
||||
}
|
||||
}
|
||||
|
||||
auto create_sizer_with_btn = [](wxWindow* parent, Button** btn, const std::string& icon_name, const wxString& label) {
|
||||
*btn = new Button(parent, label);
|
||||
(*btn)->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
|
||||
@@ -725,7 +669,7 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
}
|
||||
|
||||
// For bbl printers, show option to control the device tab
|
||||
if (wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
if (wxGetApp().preset_bundle->is_bbl_vendor() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
m_optgroup->show_field("bbl_use_print_host_webui");
|
||||
const bool use_print_host_webui = !current_webui.empty();
|
||||
if (Field* printhost_webui_field = m_optgroup->get_field("bbl_use_print_host_webui"); printhost_webui_field) {
|
||||
@@ -816,31 +760,6 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printer_agent_type()
|
||||
{
|
||||
if (m_config == nullptr)
|
||||
return;
|
||||
|
||||
Field* agent_field = m_optgroup->get_field("printer_agent");
|
||||
if (!agent_field)
|
||||
return;
|
||||
|
||||
Choice* agent_choice = dynamic_cast<Choice*>(agent_field);
|
||||
if (!agent_choice)
|
||||
return;
|
||||
|
||||
// Sync selection with current config value
|
||||
const std::string current_agent = m_config->opt_string("printer_agent");
|
||||
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
for (size_t i = 0; i < agents.size(); ++i) {
|
||||
if (agents[i].id == current_agent) {
|
||||
agent_choice->set_value(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printers()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
@@ -894,11 +813,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||
{
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name);
|
||||
event.Skip();
|
||||
|
||||
// Defer printer agent switch to ensure preset save completes first
|
||||
wxGetApp().CallAfter([] {
|
||||
wxGetApp().switch_printer_agent();
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
void update(bool printer_change = false);
|
||||
void update_host_type(bool printer_change);
|
||||
void update_printer_agent_type();
|
||||
void update_preset_input();
|
||||
void update_printhost_buttons();
|
||||
void update_printers();
|
||||
|
||||
@@ -33,7 +33,7 @@ LayerNumberTextInput::LayerNumberTextInput(wxWindow* parent, int layer_number, w
|
||||
{
|
||||
GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_DIGITS));
|
||||
GetTextCtrl()->SetFont(::Label::Body_14);
|
||||
Append(_L("End"));
|
||||
Append(_L_CONTEXT("End", "Layer range"));
|
||||
Append(_L("Customize"));
|
||||
if (m_value_type == ValueType::End)
|
||||
SetSelection(0);
|
||||
@@ -45,7 +45,7 @@ LayerNumberTextInput::LayerNumberTextInput(wxWindow* parent, int layer_number, w
|
||||
Bind(wxEVT_TEXT, [this](auto& evt) {
|
||||
if (m_value_type == ValueType::End) {
|
||||
// TextCtrl->SetValue() will generate a wxEVT_TEXT event
|
||||
GetTextCtrl()->ChangeValue(_L("End"));
|
||||
GetTextCtrl()->ChangeValue(_L_CONTEXT("End", "Layer range"));
|
||||
return;
|
||||
}
|
||||
evt.Skip();
|
||||
|
||||
+186
-82
@@ -3247,7 +3247,8 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
|
||||
auto p_mainframe = wxGetApp().mainframe;
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
const bool use_native_device_tab = preset_bundle.use_bbl_device_tab() || NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
const bool use_native_device_tab = preset_bundle.use_bbl_device_tab() || use_printer_agents;
|
||||
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
//only show connection button for not-BBL printer
|
||||
@@ -3259,7 +3260,8 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
p_mainframe->set_print_button_to_default(MainFrame::PrintSelectType::ePrintPlate);
|
||||
} else {
|
||||
//p->btn_connect_printer->Show();
|
||||
p->m_printer_connect->Show();
|
||||
// ORCA: hide the physical-printer connection button when printer agents are enabled
|
||||
p->m_printer_connect->Show(!use_printer_agents);
|
||||
|
||||
// ORCA: show/hide sync-ams button based on filament sync mode
|
||||
auto agent = wxGetApp().getAgent();
|
||||
@@ -3281,10 +3283,14 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
print_btn_type = preset_bundle.is_bbl_vendor() ? MainFrame::PrintSelectType::ePrintPlate : MainFrame::PrintSelectType::eSendGcode;
|
||||
print_btn_type = (preset_bundle.is_bbl_vendor() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
? MainFrame::PrintSelectType::ePrintPlate
|
||||
: MainFrame::PrintSelectType::eSendGcode;
|
||||
}
|
||||
|
||||
if (!use_native_device_tab)
|
||||
if (use_printer_agents)
|
||||
p_mainframe->load_printer_url();
|
||||
else if (!use_native_device_tab)
|
||||
p_mainframe->load_printer_url(url, apikey);
|
||||
|
||||
|
||||
@@ -3440,7 +3446,10 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
|
||||
bool isBBL = preset_bundle.is_bbl_vendor();
|
||||
bool is_dual_extruder = extruder_variants->size() == 2;
|
||||
p->layout_printer(preset_bundle.use_bbl_network(), isBBL && is_dual_extruder);
|
||||
// why: agent mode drives the native device tab, so the sidebar lays out like BBL
|
||||
// (no physical-printer connect button).
|
||||
p->layout_printer(preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"),
|
||||
isBBL && is_dual_extruder);
|
||||
|
||||
// Update nozzle titles from printer config (e.g. "Main Nozzle" / "Auxiliary Nozzle" for N6)
|
||||
// UI left = DEPUTY_EXTRUDER_ID(1), UI right = MAIN_EXTRUDER_ID(0)
|
||||
@@ -5633,6 +5642,7 @@ struct Plater::priv
|
||||
void on_action_slice_all(SimpleEvent&);
|
||||
void on_action_publish(wxCommandEvent &evt);
|
||||
void on_action_print_plate(SimpleEvent&);
|
||||
void open_machine_select_dialog(int plate_idx, PrintFromType print_type = PrintFromType::FROM_NORMAL);
|
||||
void on_action_print_all(SimpleEvent&);
|
||||
void on_action_export_gcode(SimpleEvent&);
|
||||
void on_action_send_gcode(SimpleEvent&);
|
||||
@@ -5781,6 +5791,8 @@ private:
|
||||
bool show_warning_dialog { false };
|
||||
};
|
||||
|
||||
Plater::~Plater() = default;
|
||||
|
||||
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf)", std::regex::icase);
|
||||
const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
|
||||
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
|
||||
@@ -5795,7 +5807,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
|
||||
#endif // WIN32
|
||||
|
||||
m_mainframe.Raise();
|
||||
m_mainframe.select_tab(size_t(MainFrame::tp3DEditor));
|
||||
m_mainframe.select_tab(TAB_ID_PREPARE);
|
||||
if (wxGetApp().is_editor())
|
||||
m_plater.select_view_3D("3D");
|
||||
|
||||
@@ -6598,9 +6610,9 @@ void Plater::priv::select_next_view_3D()
|
||||
{
|
||||
|
||||
if (current_panel == view3D)
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
else if (current_panel == preview)
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
// else if (current_panel == assemble_view)
|
||||
// set_current_panel(view3D);
|
||||
}
|
||||
@@ -7899,7 +7911,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
q->select_plate(first_plate_index);
|
||||
//set to 3d tab
|
||||
q->select_view_3D("Preview");
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
}
|
||||
else {
|
||||
//set to 3d tab
|
||||
@@ -7918,7 +7930,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
else {
|
||||
//always set to 3D after loading files
|
||||
q->select_view_3D("3D");
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
|
||||
if (load_model) {
|
||||
@@ -8826,7 +8838,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (inst_idx != -1) {
|
||||
auto* model = wxGetApp().obj_list()->GetModel();
|
||||
@@ -8855,7 +8867,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
|
||||
} else {
|
||||
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
|
||||
if (iter != objects.end()) {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
|
||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
||||
}
|
||||
@@ -9522,7 +9534,7 @@ void Plater::priv::replace_all_with_stl()
|
||||
return;
|
||||
}
|
||||
|
||||
std::string status = _L("Replaced with 3D files from directory:\n").ToStdString() + out_path.string() + "\n\n";
|
||||
wxString status = _L("Replaced with 3D files from directory:\n") + from_u8(out_path.string()) + "\n\n";
|
||||
|
||||
for (unsigned int idx : volume_idxs) {
|
||||
const GLVolume* v = selection.get_volume(idx);
|
||||
@@ -9542,13 +9554,13 @@ void Plater::priv::replace_all_with_stl()
|
||||
std::string volume_name = volume->name;
|
||||
|
||||
if (new_path == input_path) {
|
||||
status += boost::str(boost::format(_L("✖ Skipped %1%: same file.\n").ToStdString()) % volume_name);
|
||||
status += wxString::Format(_L("✖ Skipped %s: same file.\n"), from_u8(volume_name));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " skipping replace volume : same filename " << new_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fs::exists(new_path)) {
|
||||
status += boost::str(boost::format(_L("✖ Skipped %1%: file does not exist.\n").ToStdString()) % volume_name);
|
||||
status += wxString::Format(_L("✖ Skipped %s: file does not exist.\n"), from_u8(volume_name));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : filen does not exist " << new_path;
|
||||
continue;
|
||||
}
|
||||
@@ -9556,12 +9568,12 @@ void Plater::priv::replace_all_with_stl()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " replacing volume : " << input_path << " with " << new_path;
|
||||
|
||||
if (!replace_volume_with_stl(object_idx, volume_idx, new_path, _u8L("Replace with 3D file"))) {
|
||||
status += boost::str(boost::format(_L("✖ Skipped %1%: failed to replace.\n").ToStdString()) % volume_name);
|
||||
status += wxString::Format(_L("✖ Skipped %s: failed to replace.\n"), from_u8(volume_name));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cannot replace volume : failed to replace with " << new_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
status += boost::str(boost::format(_L("✔ Replaced %1%.\n").ToStdString()) % volume_name);
|
||||
status += wxString::Format(_L("✔ Replaced %s.\n"), from_u8(volume_name));
|
||||
}
|
||||
|
||||
// update 3D scene
|
||||
@@ -11195,18 +11207,23 @@ void Plater::priv::on_action_print_plate(SimpleEvent&)
|
||||
}
|
||||
|
||||
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL);
|
||||
m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index());
|
||||
m_select_machine_dlg->ShowModal();
|
||||
if (preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
open_machine_select_dialog(partplate_list.get_curr_plate_index());
|
||||
} else {
|
||||
q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::open_machine_select_dialog(int plate_idx, PrintFromType print_type)
|
||||
{
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(print_type);
|
||||
m_select_machine_dlg->prepare(plate_idx);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
}
|
||||
|
||||
void Plater::priv::on_action_send_to_multi_machine(SimpleEvent&)
|
||||
{
|
||||
if (!m_send_multi_dlg)
|
||||
@@ -11222,10 +11239,7 @@ void Plater::priv::on_action_print_plate_from_sdcard(SimpleEvent&)
|
||||
}
|
||||
|
||||
//BBS
|
||||
if (!m_select_machine_dlg) m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_SDCARD_VIEW);
|
||||
m_select_machine_dlg->prepare(0);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
open_machine_select_dialog(0, PrintFromType::FROM_SDCARD_VIEW);
|
||||
}
|
||||
|
||||
void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
@@ -11237,16 +11251,22 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
}
|
||||
|
||||
const int new_sel = e.GetSelection();
|
||||
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
|
||||
if (new_sel == wxNOT_FOUND) {
|
||||
// GetPage(new_sel) below needs a valid index.
|
||||
e.Skip();
|
||||
return;
|
||||
}
|
||||
const wxString new_name = main_frame->m_tabpanel->GetPageName(new_sel);
|
||||
sidebar_layout.show = new_name == TAB_ID_PREPARE || new_name == TAB_ID_PREVIEW;
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
const bool is_printer_agent_plugin = NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || is_printer_agent_plugin);
|
||||
if (use_native_device_tab && new_sel == MainFrame::tpMonitor) {
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents);
|
||||
if (use_native_device_tab && new_name == TAB_ID_MONITOR) {
|
||||
// BBL network module is only required for BBL-vendor printers.
|
||||
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
|
||||
if (!is_printer_agent_plugin && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
e.Veto();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
||||
if (q) {
|
||||
@@ -11255,9 +11275,17 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
|
||||
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
|
||||
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
|
||||
// TAB_ID_MONITOR itself.
|
||||
const bool selecting_web_device_tab = main_frame->m_printer_view &&
|
||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
||||
if (selecting_web_device_tab) {
|
||||
// Use the selected discovered machine when the preset has no host.
|
||||
main_frame->load_printer_url();
|
||||
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
|
||||
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
if (main_frame->m_printer_view && url.empty()) {
|
||||
// It's missing_connection page, reload so that we can replay the gif image
|
||||
main_frame->m_printer_view->reload();
|
||||
@@ -11302,13 +11330,8 @@ void Plater::priv::on_action_print_all(SimpleEvent&)
|
||||
}
|
||||
|
||||
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL);
|
||||
m_select_machine_dlg->prepare(PLATE_ALL_IDX);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
if (preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
open_machine_select_dialog(PLATE_ALL_IDX);
|
||||
} else {
|
||||
q->send_gcode_legacy(PLATE_ALL_IDX, nullptr);
|
||||
}
|
||||
@@ -12017,12 +12040,53 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
|
||||
auto nozzle_volumes_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
assert(obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2);
|
||||
if (obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2) {
|
||||
// Map device flow->volume via the table, not `flowtype - 1` (which mis-maps U_FLOW to nvtHybrid).
|
||||
NozzleVolumeType right_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0));
|
||||
NozzleVolumeType left_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(1));
|
||||
NozzleVolumeType preset_left_type = NozzleVolumeType(nozzle_volumes_values[0]);
|
||||
NozzleVolumeType preset_right_type = NozzleVolumeType(nozzle_volumes_values[1]);
|
||||
is_same_as_printer = (left_nozzle_type == preset_left_type && right_nozzle_type == preset_right_type);
|
||||
// [Vortek] H2C: Use BBS-style NozzleGroupInfo comparison instead of direct nozzle type match.
|
||||
// This correctly handles Hybrid presets (which expand into per-type counts) and detects
|
||||
// never-synced state (nozzle_count==0) so the first sync dialog appears.
|
||||
// After device sync, extruder_nozzle_stats matches printer → dialog suppressed.
|
||||
// Reference to BBS: BambuStudio/src/slic3r/GUI/Plater.cpp is_extruder_stat_synced()
|
||||
using namespace MultiNozzleUtils;
|
||||
auto nozzle_diameter_values = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values;
|
||||
|
||||
// Build preset nozzle groups from extruder_nozzle_stats config
|
||||
std::vector<std::vector<NozzleGroupInfo>> preset_nozzle_infos(nozzle_diameter_values.size());
|
||||
for (size_t extruder_id = 0; extruder_id < nozzle_diameter_values.size(); ++extruder_id) {
|
||||
NozzleVolumeType preset_volume_type = NozzleVolumeType(nozzle_volumes_values[extruder_id]);
|
||||
std::string preset_diameter = format_diameter_to_str(nozzle_diameter_values[extruder_id]);
|
||||
|
||||
if (preset_volume_type == nvtHybrid) {
|
||||
// Hybrid: expand into separate groups for each nozzle type from stats
|
||||
int std_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard);
|
||||
int hf_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow);
|
||||
if (std_count > 0)
|
||||
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, nvtStandard, extruder_id, std_count);
|
||||
if (hf_count > 0)
|
||||
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, nvtHighFlow, extruder_id, hf_count);
|
||||
// If both are 0 → never synced → empty group → will mismatch
|
||||
} else {
|
||||
int count = getExtruderNozzleCount(preset_bundle, extruder_id, preset_volume_type);
|
||||
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, preset_volume_type, extruder_id, count);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare with printer nozzle groups
|
||||
auto printer_groups = obj->GetNozzleSystem()->GetNozzleGroups();
|
||||
for (const auto& preset_groups : preset_nozzle_infos) {
|
||||
for (const auto& preset_group : preset_groups) {
|
||||
if (preset_group.nozzle_count == 0) {
|
||||
// Never synced: if printer has nozzles of this type → needs sync
|
||||
if (std::find_if(printer_groups.begin(), printer_groups.end(),
|
||||
[&preset_group](const NozzleGroupInfo& elem) { return preset_group.is_same_type(elem); })
|
||||
!= printer_groups.end()) {
|
||||
is_same_as_printer = false;
|
||||
break;
|
||||
}
|
||||
} else if (std::find(printer_groups.begin(), printer_groups.end(), preset_group) == printer_groups.end()) {
|
||||
is_same_as_printer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::map<int, int>> ams_count_info;
|
||||
@@ -12082,7 +12146,7 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
|
||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||
else
|
||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
||||
wxGetApp().mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tpPreview);
|
||||
wxGetApp().mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -12650,7 +12714,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
|
||||
ModelWipeTower& tower = model.wipe_tower;
|
||||
|
||||
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
|
||||
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
|
||||
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
}
|
||||
const GLGizmosManager& gizmos = get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView ? assemble_view->get_canvas3d()->get_gizmos_manager() : view3D->get_canvas3d()->get_gizmos_manager();
|
||||
@@ -12760,7 +12824,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
|
||||
ModelWipeTower& tower = model.wipe_tower;
|
||||
|
||||
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
|
||||
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
|
||||
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
}
|
||||
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
|
||||
@@ -13039,7 +13103,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
|
||||
get_notification_manager()->clear_all();
|
||||
|
||||
if (!silent)
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
//get_partplate_list().reinit();
|
||||
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
|
||||
@@ -13188,7 +13252,7 @@ void Plater::load_project(wxString const& filename2,
|
||||
if (!m_exported_file) {
|
||||
p->select_view("topfront");
|
||||
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
}
|
||||
else {
|
||||
p->partplate_list.select_plate_view();
|
||||
@@ -13302,7 +13366,7 @@ void Plater::import_model_id(wxString download_info)
|
||||
const int max_retries = 3;
|
||||
|
||||
/* jump to 3D eidtor */
|
||||
wxGetApp().mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
/* prepare progress dialog */
|
||||
bool cont = true;
|
||||
@@ -13611,7 +13675,7 @@ void Plater::calib_pa(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
|
||||
new_project(false, false, calib_pa_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
print_config->set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||
@@ -14092,7 +14156,7 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
|
||||
if (new_project(false, false, calib_name) == wxID_CANCEL)
|
||||
return;
|
||||
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
|
||||
if (is_linear) {
|
||||
if (pass == 1)
|
||||
@@ -14129,7 +14193,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
|
||||
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
|
||||
new_project(false, false, calib_temp_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Temp_Tower) return;
|
||||
|
||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
|
||||
@@ -14171,7 +14235,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
}
|
||||
}
|
||||
|
||||
if (std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
if (params.nozzle_based_resize && std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale);
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
@@ -14179,7 +14243,9 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature_initial_layer", (int) start_temp);
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature", (int) start_temp);
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
// When resizing is disabled the 0.4 mm / 0.2 mm reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize)
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14190,7 +14256,8 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
if (params.nozzle_based_resize)
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
|
||||
|
||||
changed_objects({ 0 });
|
||||
@@ -14206,7 +14273,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
|
||||
new_project(false, false, calib_vol_speed_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
|
||||
return;
|
||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
|
||||
@@ -14285,7 +14352,7 @@ void Plater::calib_retraction(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_retraction_name = wxString::Format(L"Retraction");
|
||||
new_project(false, false, calib_retraction_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Retraction_tower)
|
||||
return;
|
||||
|
||||
@@ -14345,7 +14412,7 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_vfa_name = wxString::Format(L"VFA test");
|
||||
new_project(false, false, calib_vfa_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_VFA_Tower)
|
||||
return;
|
||||
|
||||
@@ -14354,6 +14421,42 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
|
||||
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
|
||||
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
|
||||
double nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
|
||||
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
|
||||
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
|
||||
}
|
||||
if (nozzle_diameter <= 0.0)
|
||||
nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
|
||||
// Resolved layer height: use the (possibly auto-adjusted) value from the dialog, else default to nozzle/2.
|
||||
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
|
||||
|
||||
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
|
||||
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
|
||||
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
|
||||
// of exactly on it, which would otherwise add a degenerate extra layer.
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
// When resizing is enabled, XY scales with the nozzle (footprint / line width) and Z scales so each base
|
||||
// block becomes vfa_layers_per_block layers of the resolved layer height. When disabled the 0.4 mm / 0.2 mm
|
||||
// reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize) {
|
||||
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
|
||||
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
|
||||
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(xy_scale, xy_scale, z_scale);
|
||||
}
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 });
|
||||
set_config_values<bool, ConfigOptionBoolsNullable>(print_config, "enable_overhang_speed", false);
|
||||
@@ -14367,6 +14470,10 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
print_config->set_key_value("spiral_mode", new ConfigOptionBool(true));
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
print_config->set_key_value("precise_z_height", new ConfigOptionBool(false));
|
||||
if (params.nozzle_based_resize) {
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||
}
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14377,21 +14484,18 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
|
||||
|
||||
// cut upper
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = 5 * ((params.end - params.start) / params.step + 1);
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
p->background_process.fff_print()->set_calib_params(params);
|
||||
// Pass the resolved layer height on (only meaningful when resized). GCode's VFA stepping is layer-based, so
|
||||
// it does not require it, but keep it consistent with the geometry.
|
||||
Calib_Params calib_params = params;
|
||||
calib_params.vfa_layer_height = params.nozzle_based_resize ? layer_height : 0.0;
|
||||
p->background_process.fff_print()->set_calib_params(calib_params);
|
||||
}
|
||||
|
||||
void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
|
||||
new_project(false, false, calib_input_shaping_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Input_shaping_freq)
|
||||
return;
|
||||
|
||||
@@ -14457,7 +14561,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
|
||||
{
|
||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
|
||||
new_project(false, false, calib_input_shaping_name);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Input_shaping_damp)
|
||||
return;
|
||||
|
||||
@@ -14522,7 +14626,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
|
||||
{
|
||||
const auto Calib_Cornering = wxString::Format(L"Cornering test");
|
||||
new_project(false, false, Calib_Cornering);
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
if (params.mode != CalibMode::Calib_Cornering)
|
||||
return;
|
||||
|
||||
@@ -14655,7 +14759,7 @@ void Plater::load_gcode(const wxString& filename)
|
||||
//p->gcode_result.reset();
|
||||
//reset_gcode_toolpaths();
|
||||
p->preview->reload_print(m_only_gcode);
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||
p->set_current_panel(p->preview, true);
|
||||
p->get_current_canvas3D()->render();
|
||||
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
|
||||
@@ -15082,7 +15186,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
|
||||
|
||||
m_sizer_main->Add(dlg_btns, 0, wxEXPAND);
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
@@ -15328,7 +15432,7 @@ LoadType determine_load_type(std::string filename, std::string override_setting)
|
||||
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
|
||||
|
||||
// BBS: jump to plater panel
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
return load_type;
|
||||
}
|
||||
|
||||
@@ -15557,7 +15661,7 @@ void Plater::reset_with_confirm()
|
||||
.ShowModal() == wxID_YES) {
|
||||
reset();
|
||||
// BBS: jump to plater panel
|
||||
wxGetApp().mainframe->select_tab(size_t(0));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_HOME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17371,7 +17475,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
|
||||
//BBS
|
||||
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
|
||||
{
|
||||
p->main_frame->request_select_tab(MainFrame::TabPosition::tpCalibration);
|
||||
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
|
||||
auto calibration_panel = p->main_frame->m_calibration;
|
||||
if (calibration_panel) {
|
||||
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
|
||||
@@ -17403,7 +17507,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
||||
if (!dev) return;
|
||||
|
||||
dev->set_selected_machine(evt.GetString().ToStdString());
|
||||
p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||
//jump to monitor and select device status panel
|
||||
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||
if(curr_monitor)
|
||||
@@ -17418,7 +17522,7 @@ void Plater::send_job_finished(wxCommandEvent& evt)
|
||||
|
||||
send_gcode_finish(evt.GetString());
|
||||
p->hide_send_to_printer_dlg();
|
||||
//p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
//p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||
////jump to monitor and select device status panel
|
||||
//MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||
//if (curr_monitor)
|
||||
@@ -18339,7 +18443,7 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
|
||||
MessageDialog dlg(this, content, title, wxOK | wxFORWARD | wxICON_WARNING, _L("Device Page"));
|
||||
auto result = dlg.ShowModal();
|
||||
if (result == wxFORWARD) {
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpMonitor));
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_MONITOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ public:
|
||||
Plater(const Plater &) = delete;
|
||||
Plater &operator=(Plater &&) = delete;
|
||||
Plater &operator=(const Plater &) = delete;
|
||||
~Plater() = default;
|
||||
~Plater();
|
||||
|
||||
bool Show(bool show = true);
|
||||
|
||||
@@ -1027,4 +1027,4 @@ wxArrayString get_all_camera_view_type();
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Slic3r
|
||||
// IMPORTANT: ordinal order is the Plugins dialog Source sort priority.
|
||||
Mine,
|
||||
Subscribed,
|
||||
Orphaned,
|
||||
Local
|
||||
};
|
||||
|
||||
@@ -20,6 +21,7 @@ namespace Slic3r
|
||||
{
|
||||
case PluginSource::Mine: return "mine";
|
||||
case PluginSource::Subscribed: return "subscribed";
|
||||
case PluginSource::Orphaned: return "orphaned";
|
||||
case PluginSource::Local: return "local";
|
||||
}
|
||||
|
||||
|
||||
@@ -15,46 +15,14 @@ namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// Low-specificity element defaults (no !important) for UNSTYLED plugin HTML, so a bare
|
||||
// plugin page looks native while any CSS the plugin ships still wins. Built on the
|
||||
// --orca-* variables the host injects (see WebViewHostDialog); document-start injected
|
||||
// AFTER the host contract so the variables are defined (shares the base injector's
|
||||
// WebView2 timing guard).
|
||||
std::string plugin_defaults_user_script()
|
||||
{
|
||||
std::string css;
|
||||
css += "<style id=\"orca-plugin-defaults\">";
|
||||
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
|
||||
"font-family:var(--orca-font);font-size:13px;}";
|
||||
css += "body{margin:0;}";
|
||||
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
|
||||
css += "a{color:var(--orca-accent);}";
|
||||
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
|
||||
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
|
||||
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
|
||||
css += "button:hover{filter:brightness(1.1);}";
|
||||
css += "button:disabled{opacity:.5;cursor:default;}";
|
||||
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
|
||||
"background:var(--orca-bg);border:1px solid var(--orca-border);"
|
||||
"border-radius:4px;padding:4px 8px;}";
|
||||
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
|
||||
css += "table{border-collapse:collapse;}";
|
||||
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
|
||||
css += "th{color:var(--orca-muted);font-weight:600;}";
|
||||
css += "::-webkit-scrollbar{width:12px;height:12px;}";
|
||||
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
|
||||
css += "::-webkit-scrollbar-track{background:transparent;}";
|
||||
css += "</style>";
|
||||
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
||||
}
|
||||
|
||||
// Injected into every page at document start (before the plugin's own scripts).
|
||||
// Defines window.orca as the only host surface the page may use. It references
|
||||
// window.wx lazily (at call time) so it never races the backend's deferred
|
||||
// registration of the "wx" message handler. Guarded against double-injection so
|
||||
// it is harmless if also prepended.
|
||||
// Injected into the top-level page at document start (before the plugin's own
|
||||
// scripts). Defines window.orca as the only host surface the page may use. It
|
||||
// references window.wx lazily (at call time) so it never races the backend's
|
||||
// deferred registration of the "wx" message handler. Guarded against
|
||||
// double-injection so it is harmless if also prepended.
|
||||
constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
||||
(function () {
|
||||
if (window.top !== window.self) return;
|
||||
if (window.orca) return;
|
||||
var handlers = [];
|
||||
function send(kind, data) {
|
||||
@@ -128,7 +96,7 @@ PluginWebDialog::PluginWebDialog(wxWindow* parent,
|
||||
void PluginWebDialog::add_user_scripts()
|
||||
{
|
||||
if (wxWebView* wv = browser()) {
|
||||
wv->AddUserScript(wxString::FromUTF8(plugin_defaults_user_script()));
|
||||
wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
|
||||
wv->AddUserScript(ORCA_BRIDGE_JS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ struct PluginDialogItem
|
||||
bool loading = false;
|
||||
|
||||
bool is_cloud_plugin = false;
|
||||
bool orphaned = false;
|
||||
bool has_local_package = false;
|
||||
bool unauthorized = false;
|
||||
bool has_script_capability = false;
|
||||
@@ -138,6 +139,9 @@ PluginDescriptor as_cloud_only_descriptor(PluginDescriptor descriptor)
|
||||
descriptor.plugin_root.clear();
|
||||
descriptor.entry_path.clear();
|
||||
descriptor.installed_version.clear();
|
||||
// No local package is left behind, so the package verdict and any load error it produced go with it.
|
||||
descriptor.metadata_valid = false;
|
||||
descriptor.clear_error();
|
||||
if (descriptor.cloud.has_value()) {
|
||||
descriptor.cloud->installed = false;
|
||||
descriptor.cloud->update_available = false;
|
||||
@@ -243,6 +247,7 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
|
||||
payload_item["sharing_token"] = dialog_item.sharing_token;
|
||||
payload_item["thumbnail_url"] = dialog_item.thumbnail_url;
|
||||
payload_item["installed"] = dialog_item.has_local_package;
|
||||
payload_item["orphaned"] = dialog_item.orphaned;
|
||||
payload_item["installed_version"] = dialog_item.installed_version;
|
||||
payload_item["latest_version"] = dialog_item.latest_version;
|
||||
return payload_item;
|
||||
@@ -265,7 +270,8 @@ PluginSource derive_plugin_source(const PluginDescriptor& descriptor)
|
||||
const bool is_cloud = descriptor.is_cloud_plugin();
|
||||
const bool is_mine = is_cloud && has_cloud_meta && descriptor.cloud->is_mine;
|
||||
|
||||
// Source is ownership/locality only; issue states never replace this badge.
|
||||
if (is_cloud && has_cloud_meta && descriptor.cloud->orphaned)
|
||||
return PluginSource::Orphaned;
|
||||
if (is_mine)
|
||||
return PluginSource::Mine;
|
||||
if (is_cloud)
|
||||
@@ -278,29 +284,36 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
PluginAvailableActions available_actions;
|
||||
const bool is_loading = item.status == PluginStatus::Loading;
|
||||
const bool is_cloud = item.is_cloud_plugin;
|
||||
const bool is_orphaned = item.orphaned;
|
||||
const bool is_mine = item.source == PluginSource::Mine;
|
||||
const bool has_local = item.has_local_package;
|
||||
const bool authorized_for_install = !item.unauthorized;
|
||||
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !has_local && authorized_for_install;
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !is_orphaned && !has_local && authorized_for_install;
|
||||
available_actions.can_toggle = !is_loading && (has_local || available_actions.toggle_installs_cloud_plugin);
|
||||
|
||||
auto add_action = [&available_actions](const char* id, const char* label, bool enabled = true, bool danger = false) {
|
||||
available_actions.context_actions.push_back(PluginContextAction{id, label, enabled, danger});
|
||||
};
|
||||
|
||||
if (is_cloud) {
|
||||
if (is_mine)
|
||||
add_action("delete_mine_plugin", "Delete", true, true);
|
||||
else
|
||||
add_action("unsubscribe_plugin", "Unsubscribe", true, true);
|
||||
// Owned cloud plugins fall through to the local delete: it removes the installed package only.
|
||||
// Deleting a plugin from the cloud is a plugin hub operation and is never offered here.
|
||||
if (is_cloud && !is_orphaned && !is_mine) {
|
||||
add_action("unsubscribe_plugin", "Unsubscribe", true, true);
|
||||
} else if (has_local) {
|
||||
add_action("delete_plugin", "Delete", true, true);
|
||||
}
|
||||
|
||||
add_action("open_folder", "Show in folder", has_local);
|
||||
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
if (!is_orphaned) {
|
||||
if (is_cloud) {
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
} else {
|
||||
add_action("reload_plugin", "Reload");
|
||||
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||
}
|
||||
}
|
||||
|
||||
return available_actions;
|
||||
}
|
||||
@@ -353,6 +366,7 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
|
||||
item.error_text = descriptor.normalized_error();
|
||||
item.has_error = descriptor.has_error();
|
||||
item.is_cloud_plugin = descriptor.is_cloud_plugin();
|
||||
item.orphaned = descriptor.cloud.has_value() && descriptor.cloud->orphaned;
|
||||
item.has_local_package = descriptor.has_local_package();
|
||||
item.unauthorized = descriptor.is_unauthorized();
|
||||
item.is_loaded = manager.is_plugin_loaded(descriptor.plugin_key);
|
||||
@@ -585,7 +599,35 @@ bool PluginsDialog::get_descriptor(const std::string& plugin_key, PluginDescript
|
||||
|
||||
void PluginsDialog::refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud)
|
||||
{
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message);
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() {
|
||||
prompt_for_missing_plugins();
|
||||
send_plugins();
|
||||
}, title, message);
|
||||
}
|
||||
|
||||
void PluginsDialog::prompt_for_missing_plugins()
|
||||
{
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
const std::vector<PluginDescriptor> missing = manager.get_missing_plugin_descriptors();
|
||||
if (missing.empty())
|
||||
return;
|
||||
|
||||
wxString names;
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(missing.size());
|
||||
for (const PluginDescriptor& plugin : missing) {
|
||||
keys.push_back(plugin.plugin_key);
|
||||
names += "\n- ";
|
||||
names += plugin_display_name(plugin.plugin_key);
|
||||
}
|
||||
|
||||
const int result = wxMessageBox(
|
||||
wxString::Format(_L("The following installed plugins were not found on disk:\n%s\n\nRemove them from OrcaSlicer?"), names),
|
||||
_L("Missing Plugins"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
restore_z_order();
|
||||
|
||||
if (result == wxYES)
|
||||
manager.remove_missing_plugins(keys);
|
||||
}
|
||||
|
||||
void PluginsDialog::refresh_plugins()
|
||||
@@ -737,13 +779,13 @@ void PluginsDialog::handle_plugin_menu_action(const std::string& plugin_key, con
|
||||
delete_local_plugin(row_data);
|
||||
} else if (action == "unsubscribe_plugin") {
|
||||
unsubscribe_cloud_plugin(row_data);
|
||||
} else if (action == "delete_mine_plugin") {
|
||||
delete_mine_local_and_cloud_plugin(plugin_key);
|
||||
} else if (action == "reload_plugin") {
|
||||
reload_local_plugin(plugin_key, /*clear_cache=*/false);
|
||||
} else if (action == "clear_cache_reload_plugin") {
|
||||
reload_local_plugin(plugin_key, /*clear_cache=*/true);
|
||||
} else if (action == "reinstall_plugin") {
|
||||
if (row_data.is_cloud_plugin())
|
||||
reinstall_cloud_plugin(row_data);
|
||||
else
|
||||
reinstall_local_plugin(plugin_key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,19 +1106,25 @@ void PluginsDialog::open_plugin_hub()
|
||||
void PluginsDialog::delete_local_plugin(const PluginDescriptor& plugin)
|
||||
{
|
||||
const wxString plugin_name = from_u8(plugin.name);
|
||||
const int rc = wxMessageBox(wxString::Format(_L("Delete plugin \"%s\"?\n\nThis permanently removes the plugin folder."), plugin_name),
|
||||
kDeletePluginTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
const int rc = wxMessageBox(
|
||||
wxString::Format(plugin.is_cloud_plugin() ?
|
||||
_L("Delete plugin \"%s\"?\n\nThis removes the local plugin files. The plugin stays in the cloud "
|
||||
"and can be reinstalled.") :
|
||||
_L("Delete plugin \"%s\"?\n\nThis permanently removes the plugin folder."),
|
||||
plugin_name),
|
||||
kDeletePluginTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
restore_z_order();
|
||||
if (rc != wxYES)
|
||||
return;
|
||||
|
||||
auto state = std::make_shared<PluginOperationState>();
|
||||
run_with_dialog(
|
||||
[plugin_key = plugin.plugin_key, should_refresh = plugin.is_cloud_plugin(), state]() {
|
||||
[plugin_key = plugin.plugin_key, cloud_row = as_cloud_only_descriptor(plugin), state]() {
|
||||
std::string error;
|
||||
const bool succeeded = PluginManager::instance().delete_plugin(plugin_key, error);
|
||||
if (succeeded && should_refresh)
|
||||
refresh_plugin_metadata_blocking(kFetchCloudMeta);
|
||||
// Keep the cloud row listed so the plugin stays reinstallable without waiting on a cloud fetch.
|
||||
if (succeeded && cloud_row.is_cloud_plugin())
|
||||
PluginManager::instance().update_cloud_metadata(std::vector<PluginDescriptor>{cloud_row});
|
||||
store_plugin_operation_result(state, succeeded, std::move(error));
|
||||
},
|
||||
[this, state, plugin_name]() {
|
||||
@@ -1123,7 +1171,7 @@ void PluginsDialog::unsubscribe_cloud_plugin(const PluginDescriptor& plugin)
|
||||
_L("Unsubscribing plugin"), _L("Deleting local files and unsubscribing plugin..."));
|
||||
}
|
||||
|
||||
void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
|
||||
void PluginsDialog::reload_local_plugin(const std::string& plugin_key, bool clear_cache)
|
||||
{
|
||||
if (plugin_key.empty())
|
||||
return;
|
||||
@@ -1132,11 +1180,34 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
|
||||
std::pair<bool, std::string> reload_result{false, ""};
|
||||
try {
|
||||
reload_result = run_with_dialog_wait(
|
||||
[plugin_key, was_loaded]() -> std::pair<bool, std::string> {
|
||||
[plugin_key, was_loaded, clear_cache]() -> std::pair<bool, std::string> {
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
|
||||
boost::filesystem::path cache_dir;
|
||||
if (clear_cache) {
|
||||
PluginDescriptor descriptor;
|
||||
if (!manager.try_get_plugin_descriptor(plugin_key, descriptor))
|
||||
return {false, "Plugin not found."};
|
||||
|
||||
boost::filesystem::path resolved_root;
|
||||
std::string resolve_error;
|
||||
if (!resolve_allowed_plugin_root(descriptor, {get_orca_plugins_dir()},
|
||||
"Refusing to clear a plugin cache outside the local plugin directory.",
|
||||
resolved_root, resolve_error))
|
||||
return {false, resolve_error};
|
||||
cache_dir = resolved_root / "__whl_extracted__";
|
||||
}
|
||||
|
||||
if (!manager.unload_plugin(plugin_key))
|
||||
return {false, "Failed to unload plugin."};
|
||||
|
||||
if (clear_cache) {
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove_all(cache_dir, ec);
|
||||
if (ec)
|
||||
return {false, "Failed to clear plugin cache: " + ec.message()};
|
||||
}
|
||||
|
||||
manager.load_plugin(plugin_key, false);
|
||||
std::string error;
|
||||
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) || !manager.is_plugin_loaded(plugin_key))
|
||||
@@ -1219,28 +1290,4 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin)
|
||||
show_status(wxString::Format(_L("Reloaded \"%s\"."), plugin_display_name(plugin_key)), "success");
|
||||
}
|
||||
|
||||
void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin_key)
|
||||
{
|
||||
PluginDescriptor descriptor;
|
||||
const std::string display = get_descriptor(plugin_key, descriptor) ? descriptor.name : std::string{};
|
||||
const wxString plugin_name = from_u8(display.empty() ? plugin_key : display);
|
||||
const int rc = wxMessageBox(
|
||||
wxString::Format(_L("Delete plugin \"%s\" from local and cloud?\n\nThis permanently removes the local plugin files and "
|
||||
"deletes the plugin from the cloud. This action cannot be undone."),
|
||||
plugin_name),
|
||||
kDeletePluginTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
restore_z_order();
|
||||
if (rc != wxYES)
|
||||
return;
|
||||
|
||||
std::string error;
|
||||
if (!PluginManager::instance().delete_mine_local_and_cloud_plugin(plugin_key, error)) {
|
||||
show_status(error.empty() ? _L("Failed to delete plugin from local and cloud.") : from_u8(error), "error");
|
||||
return;
|
||||
}
|
||||
|
||||
send_plugins();
|
||||
show_status(wxString::Format(_L("Deleted \"%s\"."), plugin_name), "success");
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -68,6 +68,7 @@ private:
|
||||
bool get_descriptor(const std::string& plugin_key, Slic3r::PluginDescriptor& descriptor) const;
|
||||
|
||||
void refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud);
|
||||
void prompt_for_missing_plugins();
|
||||
void refresh_plugins();
|
||||
void toggle_plugin(const std::string& plugin_key, bool enabled);
|
||||
void toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, bool enabled);
|
||||
@@ -96,9 +97,8 @@ private:
|
||||
void open_plugin_folder(const Slic3r::PluginDescriptor& plugin);
|
||||
void delete_local_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||
void unsubscribe_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||
void reinstall_local_plugin(const std::string& plugin_key);
|
||||
void reload_local_plugin(const std::string& plugin_key, bool clear_cache);
|
||||
void reinstall_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||
void delete_mine_local_and_cloud_plugin(const std::string& plugin_key);
|
||||
|
||||
// In the future, we can allow users to choose which plugin version they want to install.
|
||||
template<typename Run, typename OnFinish>
|
||||
|
||||
@@ -218,6 +218,49 @@ void PrePrintChecker::add_with_link(PrintDialogStatus state, wxString msg, wxStr
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: acknowledgement-checkbox variant of add(); see add_with_link() for the shared classification.
|
||||
void PrePrintChecker::add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback)
|
||||
{
|
||||
prePrintInfo info;
|
||||
|
||||
if (is_error(state)) {
|
||||
info.level = prePrintInfoLevel::Error;
|
||||
} else if (is_warning(state)) {
|
||||
info.level = prePrintInfoLevel::Warning;
|
||||
} else {
|
||||
info.level = prePrintInfoLevel::Normal;
|
||||
}
|
||||
|
||||
if (is_error_printer(state)) {
|
||||
info.type = prePrintInfoType::Printer;
|
||||
} else if (is_error_filament(state)) {
|
||||
info.type = prePrintInfoType::Filament;
|
||||
} else if (is_warning_printer(state)) {
|
||||
info.type = prePrintInfoType::Printer;
|
||||
} else if (is_warning_filament(state)) {
|
||||
info.type = prePrintInfoType::Filament;
|
||||
}
|
||||
|
||||
info.msg = msg;
|
||||
info.checkbox_label = checkbox_label;
|
||||
info.checkbox_checked = checked;
|
||||
info.checkbox_callback = checkbox_callback;
|
||||
|
||||
switch (info.type) {
|
||||
case prePrintInfoType::Filament:
|
||||
if (std::find(filamentList.begin(), filamentList.end(), info) == filamentList.end()) {
|
||||
filamentList.push_back(info);
|
||||
}
|
||||
break;
|
||||
case prePrintInfoType::Printer:
|
||||
if (std::find(printerList.begin(), printerList.end(), info) == printerList.end()) {
|
||||
printerList.push_back(info);
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//void PrePrintMsgBoard::add(const wxString &msg, const wxString &tips, bool is_error)
|
||||
//{
|
||||
@@ -310,6 +353,18 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& infos)
|
||||
label->Show();
|
||||
m_sizer->Add(label, 0, wxBOTTOM, FromDIP(4));
|
||||
}
|
||||
|
||||
// Orca: acknowledgement checkbox rendered beneath the message (e.g. a nozzle diameter that
|
||||
// differs from the one the printer remembers, which the user may legitimately override).
|
||||
if (!info.checkbox_label.empty())
|
||||
{
|
||||
wxCheckBox* checkbox = new wxCheckBox(this, wxID_ANY, info.checkbox_label);
|
||||
checkbox->SetForegroundColour(_GetLabelColour(info));
|
||||
checkbox->SetValue(info.checkbox_checked);
|
||||
auto cb = info.checkbox_callback;
|
||||
checkbox->Bind(wxEVT_CHECKBOX, [cb](wxCommandEvent& e) { if (cb) cb(e.IsChecked()); });
|
||||
m_sizer->Add(checkbox, 0, wxBOTTOM, FromDIP(4));
|
||||
}
|
||||
}
|
||||
|
||||
this->Show();
|
||||
|
||||
@@ -26,6 +26,9 @@ struct prePrintInfo
|
||||
wxString wiki_url;
|
||||
wxString link_label; // optional: clickable text appended after msg
|
||||
std::function<void()> link_callback; // optional: internal action for link_label click
|
||||
wxString checkbox_label; // optional: an acknowledgement checkbox under msg
|
||||
bool checkbox_checked{false};
|
||||
std::function<void(bool)> checkbox_callback; // called with the new state on toggle
|
||||
int index{0};
|
||||
|
||||
public:
|
||||
@@ -33,8 +36,9 @@ public:
|
||||
return level == other.level && type == other.type &&
|
||||
msg == other.msg && tips == other.tips &&
|
||||
wiki_url == other.wiki_url && link_label == other.link_label &&
|
||||
checkbox_label == other.checkbox_label && checkbox_checked == other.checkbox_checked &&
|
||||
index == other.index;
|
||||
// link_callback excluded: std::function is not comparable
|
||||
// link_callback / checkbox_callback excluded: std::function is not comparable
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +63,6 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusInPrinting,
|
||||
PrintStatusNozzleMatchInvalid,
|
||||
PrintStatusNozzleDataInvalid,
|
||||
PrintStatusNozzleDiameterMismatch,
|
||||
PrintStatusNozzleTypeMismatch,
|
||||
PrintStatusNozzleNoMatchedHotends,
|
||||
PrintStatusNozzleRackMaximumInstalled,
|
||||
@@ -106,6 +109,9 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusFilaSwitcherSlicingNotMatch,
|
||||
PrintStatusRackNozzleNumUnmeetWarning,
|
||||
PrintStatusHasUnreliableNozzleWarning,
|
||||
// Orca: a nozzle diameter that differs from the one the printer remembers is a warning,
|
||||
// not an error, so non-standard nozzles can still be printed with.
|
||||
PrintStatusNozzleDiameterMismatch,
|
||||
PrintStatusPrinterWarningEnd,
|
||||
|
||||
// Warnings for filament
|
||||
@@ -159,6 +165,9 @@ public:
|
||||
void add(PrintDialogStatus state, wxString msg, wxString tip, const wxString& wiki_url);
|
||||
// Orca: minimal callback-link render path instead of the full style-bitmask machinery.
|
||||
void add_with_link(PrintDialogStatus state, wxString msg, wxString link_label, std::function<void()> link_callback);
|
||||
// Orca: render msg with an acknowledgement checkbox beneath it, for an overridable warning that
|
||||
// the user must tick before proceeding (checkbox_callback reports the new state).
|
||||
void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback);
|
||||
static ::std::string get_print_status_info(PrintDialogStatus status);
|
||||
|
||||
wxString get_pre_state_msg(PrintDialogStatus status);
|
||||
|
||||
@@ -700,6 +700,12 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit
|
||||
auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param)));
|
||||
input->SetToolTip(tip);
|
||||
|
||||
// ORCA: this one is only meaningful while the dimming it controls is enabled
|
||||
if (param == "preview_dim_previous_layers_brightness") {
|
||||
m_dim_previous_layers_brightness_input = input;
|
||||
input->Enable(app_config->get_bool("preview_dim_previous_layers"));
|
||||
}
|
||||
|
||||
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
if(!title2.empty()){
|
||||
@@ -1050,8 +1056,10 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview (ported from preFlight)
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
else if (param == "preview_dim_previous_layers") {
|
||||
if (m_dim_previous_layers_brightness_input)
|
||||
m_dim_previous_layers_brightness_input->Enable(app_config->get_bool(param));
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers(app_config->get_bool(param));
|
||||
@@ -1127,6 +1135,14 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
|
||||
}
|
||||
|
||||
if (param == "use_printer_agents")
|
||||
{
|
||||
// Rebuild the Device tab so the native/web-UI choice reflects the new flag
|
||||
// immediately, instead of only on the next printer-preset change or restart.
|
||||
if (wxGetApp().plater())
|
||||
wxGetApp().plater()->sidebar().update_all_preset_comboboxes();
|
||||
}
|
||||
|
||||
if (param == "enable_high_low_temp_mixed_printing") {
|
||||
if (checkbox->GetValue()) {
|
||||
const wxString warning_title = _L("Bed Temperature Difference Warning");
|
||||
@@ -1732,11 +1748,26 @@ void PreferencesDialog::create_items()
|
||||
g_sizer->Add(item_pop_up_filament_map_dialog);
|
||||
#endif
|
||||
|
||||
//// GENERAL > Plugins
|
||||
g_sizer->Add(create_item_title(_L("Plugins")), 1, wxEXPAND);
|
||||
|
||||
auto item_plugin_pages_visible_count = create_item_spinctrl(
|
||||
_L("Visible plugin pages"),
|
||||
"",
|
||||
_L("pages"),
|
||||
_L("Number of plugin pages shown as fixed tabs before the remaining pages collapse into a dropdown on the last tab."),
|
||||
SETTING_PLUGIN_PAGES_VISIBLE_COUNT,
|
||||
PLUGIN_PAGES_VISIBLE_COUNT_MIN,
|
||||
PLUGIN_PAGES_VISIBLE_COUNT_MAX,
|
||||
[](int value) { wxGetApp().mainframe->plugin_pages().set_visible_page_count(value); }
|
||||
);
|
||||
g_sizer->Add(item_plugin_pages_visible_count);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
|
||||
//////////////////////////
|
||||
//// CONTROL TAB
|
||||
//// CONTROL TAB
|
||||
/////////////////////////////////////
|
||||
m_pref_tabs->AppendItem(_L("Control"));
|
||||
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
|
||||
@@ -1914,6 +1945,28 @@ void PreferencesDialog::create_items()
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers);
|
||||
|
||||
auto item_dim_previous_layers_brightness = create_item_spinctrl(
|
||||
_L("Dimmed layer brightness"),
|
||||
"",
|
||||
_L("%"),
|
||||
_L("How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."),
|
||||
"preview_dim_previous_layers_brightness",
|
||||
0,
|
||||
99,
|
||||
// ORCA: apply the new brightness immediately to the currently loaded preview
|
||||
[](int value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers_brightness(0.01f * value);
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers_brightness);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
|
||||
@@ -2071,6 +2124,12 @@ void PreferencesDialog::create_items()
|
||||
auto item_show_unsupported = create_item_checkbox(_L("Show unsupported presets"), _L("Show incompatible/unsupported presets in the printer and filament dropdown lists. These presets cannot be selected."), "show_unsupported_presets");
|
||||
g_sizer->Add(item_show_unsupported);
|
||||
|
||||
auto item_plugin_printer_agents = create_item_checkbox(
|
||||
_L("(Experimental) Use printer agents instead of print hosts"), _L(
|
||||
"Route print jobs for non-Bambu printers through printer plug-in agents instead of the classic print-host upload flow.\nWhen disabled, OrcaSlicer uses the legacy print-host behavior."),
|
||||
"use_printer_agents");
|
||||
g_sizer->Add(item_plugin_printer_agents);
|
||||
|
||||
//// DEVELOPER > Experimental Features
|
||||
g_sizer->Add(create_item_title(_L("Experimental Features")), 1, wxEXPAND);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user