Merge remote-tracking branch 'upstream/main' into haryr/aug25-rebase

# Conflicts:
#	resources/profiles/Custom.json
#	src/libslic3r/Brim.cpp
#	src/libslic3r/GCode.cpp
#	src/libslic3r/GCode.hpp
#	src/libslic3r/Preset.cpp
#	src/slic3r/GUI/3DScene.cpp
#	src/slic3r/GUI/ConfigManipulation.cpp
#	src/slic3r/GUI/GLCanvas3D.cpp
#	src/slic3r/GUI/Plater.cpp
This commit is contained in:
harrierpigeon
2026-08-25 06:50:46 -05:00
861 changed files with 39688 additions and 7012 deletions
+5
View File
@@ -622,6 +622,8 @@ set(SLIC3R_GUI_SOURCES
plugin/host/PluginHostSlicing.cpp
plugin/host/PluginHostUi.cpp
plugin/host/PluginHostUi.hpp
plugin/host/PluginPages.cpp
plugin/host/PluginPages.hpp
plugin/CloudPluginService.cpp
plugin/CloudPluginService.hpp
plugin/PluginFsUtils.cpp
@@ -642,6 +644,9 @@ set(SLIC3R_GUI_SOURCES
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
plugin/pluginTypes/pages/PagesPluginCapability.hpp
plugin/pluginTypes/pages/PagesPluginCapability.cpp
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
plugin/pluginTypes/script/ScriptPluginCapability.hpp
plugin/pluginTypes/script/ScriptPluginCapability.cpp
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
+3 -8
View File
@@ -432,14 +432,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
cfg.models_variants_installed.erase(it ++);
else
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
//BBS: change directoties by design
//bundle.load_configbundle((data_dir / PRESET_SYSTEM_DIR / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
bundle.load_vendor_configs_from_json((data_dir/PRESET_SYSTEM_DIR).string(), cfg.name, PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
// Orca: the version the vendor is installed at, read from its profile or —
// where the cache is the whole installation — from the cache's own stamp.
cfg.version.config_version = installed_vendor_version(cfg.name);
snapshot.vendor_configs.emplace_back(std::move(cfg));
}
+64 -14
View File
@@ -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);
// Compute up direction accounting for build plate tilt. This is frame-invariant
// (config cannot change mid-render), so compute it once before the volume loop.
Vec3f up_direction = Vec3f::UnitZ();
+89 -37
View File
@@ -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);
+5 -5
View File
@@ -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()
-1
View File
@@ -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);
}
+1 -1
View File
@@ -193,7 +193,7 @@ public:
void show_panels(CalibrationMethod method, const PrinterSeries printer_ser);
void on_device_connected(MachineObject* obj);
void on_device_connected(MachineObject* obj) override;
void update(MachineObject* obj) override;
@@ -48,8 +48,8 @@ public:
void create_page(wxWindow* parent);
void on_reset_page();
void on_device_connected(MachineObject* obj);
void on_reset_page() override;
void on_device_connected(MachineObject* obj) override;
void msw_rescale() override;
};
@@ -63,8 +63,8 @@ public:
long style = wxTAB_TRAVERSAL);
void create_page(wxWindow* parent);
void on_reset_page();
void on_device_connected(MachineObject* obj);
void on_reset_page() override;
void on_device_connected(MachineObject* obj) override;
void msw_rescale() override;
};
+71 -20
View File
@@ -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)
{
@@ -729,6 +774,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
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_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_layers);
@@ -849,14 +895,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) && !is_belt_tilted;
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
const bool have_auto_brim_ear = brim_type == btEar && !is_belt_tilted;
const bool have_painted_brim_ear = brim_type == btPainted && !is_belt_tilted;
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);
+9 -1
View File
@@ -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
+29 -39
View File
@@ -66,41 +66,41 @@ using Config::SnapshotDB;
// Configuration data structures extensions needed for the wizard
//BBS: set BBL as default
bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_bbl_bundle)
bool Bundle::load(fs::path dir, const std::string &vendor_name, bool ais_in_resources, bool ais_bbl_bundle)
{
this->preset_bundle = std::make_unique<PresetBundle>();
this->is_in_resources = ais_in_resources;
this->is_bbl_bundle = ais_bbl_bundle;
std::string path_string = source_path.string();
std::string parent_path = source_path.parent_path().string();
//BBS: add json logic for vendor bundles
std::string vendor_name = source_path.filename().string();
if (Slic3r::is_json_file(path_string)) {
// Remove the .json suffix.
vendor_name.erase(vendor_name.size() - 5);
}
else
// Orca: served from the vendor's preset cache where one covers it — which is
// how a shipped build carries its vendors — and parsed from the JSONs otherwise.
// A vendor that can be neither read nor parsed — a cache the build cannot use
// with the preset JSONs behind it pruned, say — is one the wizard cannot offer.
// Every other vendor still can be, so it is left out rather than thrown over.
size_t presets_loaded = 0;
try {
auto [config_substitutions, loaded] = preset_bundle->load_vendor_configs_from_json(
dir.string(), vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
UNUSED(config_substitutions);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
presets_loaded = loaded;
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(fatal) << boost::format("Vendor bundle: `%1%`: cannot be loaded, leaving it out: %2%") % vendor_name % e.what();
return false;
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
//BBS: add json logic for vendor bundles
auto [config_substitutions, presets_loaded] = preset_bundle->load_vendor_configs_from_json(
parent_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
UNUSED(config_substitutions);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
}
auto first_vendor = preset_bundle->vendors.begin();
if (first_vendor == preset_bundle->vendors.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % vendor_name;
return false;
}
if (presets_loaded == 0) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % vendor_name;
return false;
}
}
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % vendor_name % presets_loaded;
this->vendor_profile = &first_vendor->second;
return true;
}
@@ -125,15 +125,10 @@ BundleMap BundleMap::load()
//Orca: add custom as default
//Orca: add json logic for vendor bundle
auto orca_bundle_path = (vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
auto orca_bundle_rsrc = false;
if (!boost::filesystem::exists(orca_bundle_path)) {
orca_bundle_path = (rsrc_vendor_dir / PresetBundle::ORCA_DEFAULT_BUNDLE).replace_extension(".json");
orca_bundle_rsrc = true;
}
{
const bool from_rsrc = ! is_vendor_installed(PresetBundle::ORCA_DEFAULT_BUNDLE);
Bundle bbl_bundle;
if (bbl_bundle.load(std::move(orca_bundle_path), orca_bundle_rsrc, true))
if (bbl_bundle.load(from_rsrc ? rsrc_vendor_dir : vendor_dir, PresetBundle::ORCA_DEFAULT_BUNDLE, from_rsrc, true))
res.emplace(PresetBundle::ORCA_DEFAULT_BUNDLE, std::move(bbl_bundle));
}
@@ -141,18 +136,13 @@ BundleMap BundleMap::load()
// and then additionally from resources/profiles.
bool is_in_resources = false;
for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) {
for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) {
//BBS: add json logic for vendor bundle
if (Slic3r::is_json_file(dir_entry.path().string())) {
std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".json" part
for (const std::string &id : vendor_names_in(*dir)) {
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
Bundle bundle;
if (bundle.load(dir_entry.path(), is_in_resources))
res.emplace(std::move(id), std::move(bundle));
}
Bundle bundle;
if (bundle.load(*dir, id, is_in_resources))
res.emplace(id, std::move(bundle));
}
is_in_resources = true;
+3 -1
View File
@@ -71,9 +71,11 @@ struct Bundle
Bundle() = default;
Bundle(Bundle&& other);
// Load the vendor `vendor_name` as it is installed in `dir`, from its preset
// cache or its profile JSONs, whichever is usable.
// Returns false if not loaded. Reason for that is logged as boost::log error.
//BBS: set BBL as default
bool load(fs::path source_path, bool is_in_resources, bool is_bbl_bundle = false);
bool load(fs::path dir, const std::string &vendor_name, bool is_in_resources, bool is_bbl_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
+3 -1
View File
@@ -156,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"));
@@ -163,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);
}
+4 -15
View File
@@ -2201,25 +2201,14 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
} else {
selected_vendor_id = m_printer_preset_vendor_selected.id;
if (boost::filesystem::exists(boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR / selected_vendor_id)) {
preset_path = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string();
} else if (boost::filesystem::exists(boost::filesystem::path(Slic3r::resources_dir()) / "profiles" / selected_vendor_id)) {
preset_path = (boost::filesystem::path(Slic3r::resources_dir()) / "profiles").string();
}
if (preset_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "Preset path was not found";
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES_NO | wxYES_DEFAULT | wxCENTRE);
dlg.ShowModal();
return false;
}
try {
// Pass the app's preset bundle (which already holds OrcaFilamentLibrary) as the base
// bundle so vendor filaments that inherit OFL bases resolve via the existing
// cross-vendor inheritance path.
temp_preset_bundle.load_vendor_configs_from_json(preset_path, selected_vendor_id,
// Orca: served from the vendor's preset cache where one covers it — a shipped
// build carries that instead of the raw preset JSONs — and parsed otherwise.
temp_preset_bundle.load_vendor_configs_from_json((boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string(),
selected_vendor_id,
PresetBundle::LoadConfigBundleAttribute::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent,
wxGetApp().preset_bundle);
+104 -27
View File
@@ -10,11 +10,39 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Time.hpp"
using namespace nlohmann;
namespace {
// Orca: access_code lives on BBLocalMachine::access_code (keyed by dev_id via
// get_local_machines(), scoped by the record's own printer_agent_id field) - so binding a
// printer under one agent doesn't silently appear as already-bound under a different,
// independent agent. This only covers LAN devices (BBLocalMachine's own scope); access_code
// and user_access_code used to be the only, flat dev_id-only AppConfig keys before
// BBLocalMachine::access_code existed, and codes saved back then are still stored flat (no
// agent association at all). Since BBL was the only agent that existed at the time, honor
// those flat legacy keys as implicitly BBL's - but only for the BBL agent, so they aren't
// leaked to other agents that never bound the device themselves.
std::string get_access_code_with_legacy_fallback(Slic3r::AppConfig* config, const std::string& dev_id, const std::string& agent_id)
{
const auto& machines = config->get_local_machines();
auto it = machines.find(dev_id);
if (it != machines.end() && it->second.printer_agent_id == agent_id && !it->second.access_code.empty())
return it->second.access_code;
if (agent_id == Slic3r::BBL_PRINTER_AGENT_ID || agent_id.empty()) {
std::string code = config->get("access_code", dev_id);
if (code.empty())
code = config->get("user_access_code", dev_id);
return code;
}
return "";
}
}
namespace Slic3r
{
DeviceManager::DeviceManager(NetworkAgent* agent)
@@ -43,13 +71,13 @@ namespace Slic3r
continue;
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
obj->printer_type = m.printer_type;
obj->printer_agent_id = m.printer_agent_id;
obj->dev_connection_type = "lan";
obj->bind_state = "free";
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, obj->printer_agent_id), false);
if (obj->has_access_right()) {
localMachineList.insert(std::make_pair(m.dev_id, obj));
} else {
@@ -66,10 +94,12 @@ namespace Slic3r
if (m.is_lan_mode_printer()) {
if (m.has_access_right()) {
BBLocalMachine local_machine;
local_machine.dev_id = m.get_dev_id();
local_machine.dev_name = m.get_dev_name();
local_machine.dev_ip = m.get_dev_ip();
local_machine.printer_type = m.printer_type;
local_machine.dev_id = m.get_dev_id();
local_machine.dev_name = m.get_dev_name();
local_machine.dev_ip = m.get_dev_ip();
local_machine.printer_type = m.printer_type;
local_machine.printer_agent_id = m.printer_agent_id;
local_machine.access_code = m.get_access_code();
config->update_local_machine(local_machine);
}
} else {
@@ -132,6 +162,14 @@ namespace Slic3r
}
}
std::string DeviceManager::get_current_printer_agent_id() const
{
if (!m_agent)
return "";
auto printer_agent = m_agent->get_printer_agent();
return printer_agent ? printer_agent->get_agent_info().id : "";
}
void DeviceManager::EnableMultiMachine(bool enable)
{
m_agent->enable_multi_machine(enable);
@@ -328,6 +366,7 @@ namespace Slic3r
/* insert a new machine */
obj = new MachineObject(this, m_agent, dev_name, dev_id, dev_ip);
obj->printer_type = _parse_printer_type(printer_type_str);
obj->printer_agent_id = get_current_printer_agent_id();
obj->wifi_signal = printer_signal;
obj->dev_connection_type = connect_type;
obj->bind_state = bind_state;
@@ -339,8 +378,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, obj->printer_agent_id), false);
}
localMachineList.insert(std::make_pair(dev_id, obj));
@@ -369,6 +407,7 @@ namespace Slic3r
obj = it->second;
} else {
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
obj->printer_agent_id = get_current_printer_agent_id();
localMachineList.insert(std::make_pair(machine.dev_id, obj));
}
if (machine.printer_type.empty())
@@ -382,7 +421,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 +534,36 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
void DeviceManager::clear_other_devices(const std::string& target_agent_id)
{
// 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.
//
// Also drop "My Devices" stamped by a different agent than the one we're swapping to
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
// at this point): otherwise a device first discovered under agent A survives every swap
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
// like any other fresh device.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();)
{
const bool is_my_device = my.find(it->first) != my.end();
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
it->second->printer_agent_id != target_agent_id;
if (!is_my_device || agent_mismatch)
{
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
@@ -558,7 +626,6 @@ namespace Slic3r
}
else
{
Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning();
if (m_agent)
{
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
@@ -669,13 +736,16 @@ namespace Slic3r
m_agent->add_subscribe(subscribe_list_cache);
}
std::map<std::string, MachineObject*> DeviceManager::get_my_machine_list()
std::map<std::string, MachineObject*> DeviceManager::get_my_machine_list(const std::string& agent_id)
{
std::map<std::string, MachineObject*> result;
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
{
if (it->second && !it->second->is_lan_mode_printer())
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
continue;
if (!it->second->is_lan_mode_printer())
{
result.insert(std::make_pair(it->first, it->second));
}
@@ -683,7 +753,10 @@ namespace Slic3r
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
{
if (it->second && it->second->has_access_right() && it->second->is_avaliable() && it->second->is_lan_mode_printer())
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
continue;
if (it->second->has_access_right() && it->second->is_avaliable() && it->second->is_lan_mode_printer())
{
// remove redundant in userMachineList
if (result.find(it->first) == result.end())
@@ -695,12 +768,15 @@ namespace Slic3r
return result;
}
std::map<std::string, MachineObject*> DeviceManager::get_my_cloud_machine_list()
std::map<std::string, MachineObject*> DeviceManager::get_my_cloud_machine_list(const std::string& agent_id)
{
std::map<std::string, MachineObject*> result;
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
{
if (it->second && !it->second->is_lan_mode_printer()) { result.emplace(*it); }
if (!it->second || (!agent_id.empty() && it->second->printer_agent_id != agent_id))
continue;
if (!it->second->is_lan_mode_printer()) { result.emplace(*it); }
}
return result;
}
@@ -773,6 +849,7 @@ namespace Slic3r
else
{
obj = new MachineObject(this, m_agent, "", "", "");
obj->printer_agent_id = get_current_printer_agent_id();
if (m_agent)
{
obj->set_bind_status(m_agent->get_user_name(provider));
@@ -851,7 +928,9 @@ 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); });
}
}
@@ -878,17 +957,15 @@ namespace Slic3r
void DeviceManager::load_last_machine()
{
if (userMachineList.empty()) return;
else if (userMachineList.size() == 1) {
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
} else {
const auto& last_monitor_machine = get_user_last_machine();
if (userMachineList.find(last_monitor_machine) != userMachineList.end()) {
set_selected_machine(last_monitor_machine);
} else {
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
}
}
// 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;
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)
+16 -3
View File
@@ -48,6 +48,10 @@ 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;
@@ -70,6 +74,11 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false);
// target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check,
// just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the
// live one - this runs before the live agent is repointed.
void clear_other_devices(const std::string& target_agent_id = "");
void load_last_machine();
void update_user_machine_list_info(const std::string& provider);
void parse_user_print_info(std::string body);
@@ -84,10 +93,15 @@ public:
/* my machine*/
MachineObject* get_my_machine(std::string dev_id);
std::map<std::string, MachineObject*> get_my_machine_list();
std::map<std::string, MachineObject*> get_my_cloud_machine_list();
std::map<std::string, MachineObject*> get_my_machine_list(const std::string& agent_id = "");
std::map<std::string, MachineObject*> get_my_cloud_machine_list(const std::string& agent_id = "");
void modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider);
// id of the currently live IPrinterAgent (IPrinterAgent::get_agent_info().id), or empty if
// m_agent has no printer agent set yet. Pass to get_my_machine_list()/get_my_cloud_machine_list()
// to scope results to the active agent.
std::string get_current_printer_agent_id() const;
/* create machine or update machine properties */
void on_machine_alive(std::string json_str);
int query_bind_status(std::string& msg, const std::string& provider);
@@ -110,7 +124,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);
+1
View File
@@ -27,6 +27,7 @@ void DevStatus::ParseStatus(const nlohmann::json& print_jj)
#else
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception=" << e.what();
#endif
(void)e; // suppress C4101 when BBL_RELEASE_TO_PUBLIC
}
}
+70 -39
View File
@@ -3,6 +3,7 @@
#include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "GuiColor.hpp"
#include "GUI_App.hpp"
@@ -449,9 +450,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)
@@ -460,47 +459,46 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
if (only_refresh) {
AppConfig* config = GUI::wxGetApp().app_config;
if (config) {
if (!code.empty()) {
GUI::wxGetApp().app_config->set_str("access_code", get_dev_id(), code);
DeviceManager::update_local_machine(*this);
if (is_lan_mode_printer()) {
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
// scoped by that record's own printer_agent_id field - see the matching comment
// on get_access_code_with_legacy_fallback() in DevManager.cpp - so binding this
// device under one printer agent doesn't silently read as already-bound under a
// different, independent one. Cloud devices (the else branch below) aren't
// scoped this way: they're never recalled from a stale local cache across a
// session boundary, since parse_user_print_info() always overwrites their code
// fresh from the cloud API's current response, so there's no cross-agent leakage
// risk to guard against there.
if (!code.empty()) {
DeviceManager::update_local_machine(*this);
} else {
// Only patch an existing record's code - don't persist a brand-new
// never-bound entry just because set_access_code("") was called on it.
const auto& machines = config->get_local_machines();
auto it = machines.find(get_dev_id());
if (it != machines.end()) {
BBLocalMachine local_machine = it->second;
local_machine.access_code = "";
config->update_local_machine(local_machine);
}
// Also clear the pre-scoping flat legacy key when unbinding under BBL, so an
// old BBL-era code can't silently "re-bind" this device again via
// get_access_code_with_legacy_fallback()'s legacy fallback.
if (printer_agent_id == BBL_PRINTER_AGENT_ID || printer_agent_id.empty()) {
config->erase("access_code", get_dev_id());
config->erase("user_access_code", get_dev_id());
}
}
} else {
GUI::wxGetApp().app_config->erase("access_code", get_dev_id());
if (!code.empty())
config->set_str("access_code", get_dev_id(), code);
else
config->erase("access_code", get_dev_id());
}
}
}
}
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 +2905,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 +4644,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;
+12 -6
View File
@@ -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,13 +227,18 @@ 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 */
// id of the IPrinterAgent that was used to discover or bind this device (IPrinterAgent::get_agent_info().id,
// e.g. "bbl"), stamped at creation time — not derived from get_agent(), since m_agent is a single
// process-wide NetworkAgent shared by every MachineObject and gets repointed on agent swap
// (see DeviceManager::set_agent()), so it can't tell which agent originally found this device.
// We persist this as well so that when the printer agent is swapped, we don't show unrelated devices,
// e.g. if the current printer agent is elegoo, we shouldn't show printers connected by BBL printer agent
// under local machines.
std::string printer_agent_id;
std::string get_show_printer_type() const;
PrinterSeries get_printer_series() const;
PrinterArch get_printer_arch() const;
@@ -272,9 +276,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 */
@@ -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
@@ -26,8 +26,6 @@
#include "Widgets/HyperLink.hpp" // ORCA
#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1)
namespace Slic3r {
namespace GUI {
+1 -1
View File
@@ -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();
+175 -99
View File
@@ -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();
@@ -1403,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()
@@ -1518,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);
@@ -1651,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:
@@ -1701,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);
@@ -1772,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;
@@ -1920,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();
@@ -2067,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);
+40 -2
View File
@@ -385,7 +385,7 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value() ;
void propagate_value() override;
void set_value(const std::string& value, bool change_event = false) {
m_disable_change_event = !change_event;
@@ -440,7 +440,7 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value();
void propagate_value() override;
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style
@@ -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:
+22 -1
View File
@@ -422,7 +422,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
if (properties_shown) {
float label_w = 0.0f;
float value_w = 0.0f;
properties_rows.reserve(13);
properties_rows.reserve(14);
auto add_row = [&properties_rows, &label_w, &value_w](std::string label, std::string value) {
label_w = std::max(label_w, ImGui::CalcTextSize(label.c_str()).x);
value_w = std::max(value_w, ImGui::CalcTextSize(value.c_str()).x);
@@ -435,6 +435,27 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
add_row(_u8L("Width"), buff);
if (is_extrusion) sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), vertex.height); else strcpy(buff, NA_CSTR);
add_row(_u8L("Height"), buff);
// ORCA: Length of the move ending at the current vertex. Arc moves (G2/G3) are discretized
// into several vertices sharing the same gcode line id, so accumulate the whole run to report
// the arc length instead of the length of a single chord.
if (vertex_id > 0 && (is_extrusion || vertex.is_travel() || vertex.is_wipe())) {
const size_t vertices_count = viewer->get_vertices_count();
size_t first_id = vertex_id;
while (first_id > 0 && viewer->get_vertex_at(first_id - 1).gcode_id == vertex.gcode_id)
--first_id;
size_t last_id = vertex_id;
while (last_id + 1 < vertices_count && viewer->get_vertex_at(last_id + 1).gcode_id == vertex.gcode_id)
++last_id;
float length = 0.0f;
for (size_t i = std::max<size_t>(first_id, 1); i <= last_id; ++i) {
length += (libvgcode::convert(viewer->get_vertex_at(i).position) -
libvgcode::convert(viewer->get_vertex_at(i - 1).position)).norm();
}
sprintf(buff, ("%.3f " + _u8L("mm")).c_str(), length);
}
else
strcpy(buff, NA_CSTR);
add_row(_u8L("Length"), buff);
sprintf(buff, "%d", vertex.layer_id + 1);
add_row(_u8L("Layer"), buff);
sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate);
+127 -70
View File
@@ -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);
@@ -2873,6 +2877,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
is_belt_printer = belt_opt->value;
if (wt && !is_belt_printer && (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);
@@ -2886,7 +2893,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();
@@ -2897,51 +2904,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;
}
}
}
@@ -4210,6 +4192,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);
@@ -4223,11 +4222,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());
@@ -4324,6 +4339,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();
@@ -4429,6 +4448,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 {
@@ -4442,6 +4464,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;
}
}
@@ -4509,6 +4535,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();
}
}
}
@@ -4517,6 +4546,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;
@@ -4568,6 +4601,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;
}
@@ -4577,12 +4614,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) ||
@@ -4662,6 +4706,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
@@ -4726,7 +4774,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);
@@ -6073,9 +6123,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();
@@ -6100,7 +6159,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;
@@ -6124,6 +6182,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) {
@@ -6155,6 +6217,8 @@ void GLCanvas3D::_render_3d_navigator()
request_extra_frame();
}
m_navigator_dragging = result.dragging;
}
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
@@ -6953,7 +7017,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;
}
@@ -9172,7 +9236,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);
}
}
});
@@ -9266,8 +9330,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();
}
@@ -10593,9 +10661,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;
});
}
@@ -10729,24 +10796,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
+6
View File
@@ -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;
+6 -4
View File
@@ -18,7 +18,9 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include "boost/nowide/convert.hpp"
#endif
@@ -256,18 +258,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)
+5 -5
View File
@@ -40,11 +40,11 @@ extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts = false);
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts = false);
inline void show_error(wxWindow* parent, const std::string& message, bool has_code_excerpts = false) { show_error(parent, message.c_str(), has_code_excerpts); }
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
+157 -39
View File
@@ -307,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();
@@ -799,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) {
@@ -838,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() ||
@@ -876,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
@@ -1815,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);
}
}
@@ -2152,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());
@@ -2202,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 {
@@ -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);
});
}
@@ -3218,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
@@ -3255,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()));
@@ -3327,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();
@@ -3859,6 +3928,54 @@ 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
// why: drop stale LAN discoveries; keep My Devices, but only those belonging to the
// agent we're about to swap to, so a device stamped by the outgoing agent doesn't
// linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh.
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
// target to filter against then, so fall back to the original "keep all My Devices"
// behavior rather than guessing.
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
}
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) {
@@ -3866,24 +3983,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;
@@ -3897,7 +4007,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;
}
@@ -3920,9 +4032,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;
@@ -4498,7 +4610,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();
@@ -6705,6 +6817,12 @@ void GUI_App::add_pending_vendor_preset(const std::pair<std::string, std::map<st
// Add the corresponding vendor
std::string vendor_name = PresetBundle::find_preset_vendor(inherits_name, type);
if (vendor_name.empty()) {
// No vendor ships this preset's parent. An unnamed entry here becomes an
// unnamed bundle at install time, which nothing can install.
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no vendor carries " << inherits_name << ", skipping";
return;
}
if (need_add_vendors.find(vendor_name) == need_add_vendors.end())
need_add_vendors[vendor_name] = std::map<std::string, std::set<std::string>>();
@@ -8192,7 +8310,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());
}
}
});
@@ -9757,7 +9875,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;
}
@@ -9783,8 +9901,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);
}
@@ -9804,7 +9922,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;
}
+11 -1
View File
@@ -365,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; }
@@ -798,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);
+1
View File
@@ -125,6 +125,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},
+1 -1
View File
@@ -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;
+6 -2
View File
@@ -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) ;
+1 -1
View File
@@ -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();
+5 -2
View File
@@ -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) ;
+1 -1
View File
@@ -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);
+53 -1
View File
@@ -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__
+18 -12
View File
@@ -113,14 +113,7 @@ public:
update_dark_ui(this);
#endif
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
update_em_unit();
// recalc_font();
@@ -235,6 +228,19 @@ private:
// m_em_unit = metrics.averageWidth;
// }
// update em_unit value for new window font
void update_em_unit()
{
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
}
// check if new scale is differ from previous
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
@@ -247,8 +253,7 @@ private:
// set normal application font as a current window font
m_normal_font = this->GetFont();
// update em_unit value for new window font
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
update_em_unit();
// rescale missed controls sizes and images
on_dpi_changed(suggested_rect);
@@ -472,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__)
+1 -1
View File
@@ -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);
+37 -33
View File
@@ -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)
+3 -3
View File
@@ -85,7 +85,7 @@ public:
void update_model_object();
//ClippingPlane get_sla_clipping_plane() const;
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Entering Brim Ears"); }
@@ -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;
+5 -2
View File
@@ -566,8 +566,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event)
{
static bool was_dragging = true;
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) {
static bool was_dragging = true;
// The left up may be the end of a drag that started on the gizmo floating window (e.g. selecting
// text in the input field). Such a release is not a click on the scene and must not close the gizmo.
// (The flag is only set for left up events, so right up behavior is unchanged.)
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging && !m_parent.is_mouse_left_up_ignored()) {
// is hovered volume closest hovered?
int hovered_idx = m_parent.get_first_hover_volume_idx();
if (hovered_idx < 0)
-1
View File
@@ -597,7 +597,6 @@ void GLGizmoMeasure::on_render()
}
}
Vec3d position_on_model;
Vec3d direction_on_model;
size_t model_facet_idx = -1;
double closest_hit_distance = std::numeric_limits<double>::max();
{
+1 -1
View File
@@ -75,7 +75,7 @@ protected:
virtual void on_render() override;
virtual void on_set_state() override;
virtual CommonGizmosDataID on_get_requirements() const override;
virtual void on_render_input_window(float x, float y, float bottom_limit);
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
void on_load(cereal::BinaryInputArchive &ar) override;
void on_save(cereal::BinaryOutputArchive &ar) const override;
+1 -1
View File
@@ -67,7 +67,7 @@ protected:
void on_register_raycasters_for_picking() override;
void on_unregister_raycasters_for_picking() override;
//BBS: GUI refactor: add object manipulation
virtual void on_render_input_window(float x, float y, float bottom_limit);
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
private:
double calc_projection(const UpdateData& data) const;
+1 -1
View File
@@ -89,7 +89,7 @@ protected:
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
//BBS: GUI refactor: add object manipulation
virtual void on_render_input_window(float x, float y, float bottom_limit);
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color);
+1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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);
+17 -2
View File
@@ -2809,12 +2809,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;
@@ -3318,8 +3332,9 @@ const char* ImGuiWrapper::clipboard_get(void* user_data)
wxTextDataObject data;
wxTheClipboard->GetData(data);
if (data.GetTextLength() > 0) {
self->m_clipboard_text = into_u8(data.GetText());
const wxString text = data.GetText();
if (text.Length() > 0) {
self->m_clipboard_text = into_u8(text);
res = self->m_clipboard_text.c_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
+41 -2
View File
@@ -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(&params, &params_area, sizeof(params));
setMinimalSupportAreaPrams(params);
params.min_volume = false;
}
else {
+187 -83
View File
@@ -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();
}
}
@@ -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);
@@ -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
@@ -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();
@@ -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);
}
@@ -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()
+23 -17
View File
@@ -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 };
+9 -6
View File
@@ -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, _L_CONTEXT(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();
+106 -18
View File
@@ -9,8 +9,13 @@
#include <wx/clipbrd.h>
#include <wx/checkbox.h>
#include <wx/html/htmlwin.h>
#include <wx/html/winpars.h>
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
@@ -229,12 +234,82 @@ void MsgDialog::finalize()
}
// A placeholder-parser caret line, pointing at the column where parsing failed.
static bool is_caret_line(const std::string &line)
{
return std::count(line.begin(), line.end(), '^') == 1 &&
std::all_of(line.begin(), line.end(), [](char c) { return c == ' ' || c == '^'; });
}
// Tag each line as a code excerpt (a caret line or the source line above one) that must stay
// monospaced for the '^' to align.
static std::vector<std::pair<std::string, bool>> classify_code_lines(const std::string &msg)
{
std::vector<std::string> lines;
boost::split(lines, msg, boost::is_any_of("\n"));
for (std::string &line : lines)
if (!line.empty() && line.back() == '\r')
line.pop_back();
std::vector<std::pair<std::string, bool>> tagged;
tagged.reserve(lines.size());
for (size_t i = 0; i < lines.size(); ++i) {
bool is_code = is_caret_line(lines[i]) || (i + 1 < lines.size() && is_caret_line(lines[i + 1]));
tagged.emplace_back(std::move(lines[i]), is_code);
}
return tagged;
}
// Keeps whitespace literal so the caret's leading spaces survive.
// Used inside <code>, which supplies the fixed face. <pre> does both but adds a blank line above it.
class CodeExcerptTagHandler : public wxHtmlWinTagHandler
{
public:
wxString GetSupportedTags() override { return wxT("EXCERPT"); }
bool HandleTag(const wxHtmlTag &tag) override
{
const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
ParseInner(tag);
m_WParser->SetWhitespaceMode(ws);
return true;
}
};
// Render the message as HTML, monospacing only the code excerpts.
static std::string format_parser_error_html(const std::string &msg)
{
std::string out;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
if (!out.empty()) out += "<br>"; // join, not trail; a trailing <br> forces a scrollbar
std::string escaped = xml_escape(text);
if (is_code)
out += "<code><excerpt>" + escaped + "</excerpt></code>";
else
out += escaped;
}
return out;
}
// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
{
wxClientDC dc(parent);
int width = 0, height = 0;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
dc.SetFont(is_code ? code_font : prose_font);
width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
height += dc.GetCharHeight();
}
return wxSize(width, height);
}
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
static void add_msg_content(wxWindow *parent,
wxBoxSizer *content_sizer,
wxString msg,
bool monospaced_font = false,
bool is_marked_msg = false,
bool has_code_excerpts = false,
bool is_marked_msg = false,
const wxString &link_text = "",
std::function<void(const wxString &)> link_callback = nullptr)
{
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
// count lines in the message
int msg_lines = 0;
if (!monospaced_font) {
if (!has_code_excerpts) {
int line_len = 55;// count of symbols in one line
int start_line = 0;
for (auto i = msg.begin(); i != msg.end(); ++i) {
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
page_size = wxSize(info_width, page_height);
}
else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
wxSize msg_sz;
if (has_code_excerpts) {
msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
} else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
msg_sz = dc.GetMultiLineTextExtent(msg);
}
msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
int page_height = msg_sz.GetY();
// Reserve the horizontal scrollbar's height, or it clips the last line.
if (msg_sz.GetX() > info_width)
page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
// Extra line breaks in message dialog
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
html->Destroy();
if (msg_sz.GetX() < info_width) {//No need for line breaks
info_width = msg_sz.GetX();
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
}
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
if (monospaced_font)
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
std::string msg_escaped;
if (has_code_excerpts) {
html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
msg_escaped = format_parser_error_html(msg.ToUTF8().data());
} else {
msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
}
if (!link_text.IsEmpty() && link_callback) {
msg_escaped += "<span><a href=\"#\" style=\"color:rgb(0, 150, 136); text-decoration:underline;\">" + std::string(link_text.ToUTF8().data()) + "</a></span>";
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
, msg(temp_msg)
{
add_msg_content(this, content_sizer, msg, monospaced_font);
add_msg_content(this, content_sizer, msg, has_code_excerpts);
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
// Use a small bitmap for code excerpts, which cannot wrap and so need the width.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
SetMaxSize(MSG_DLG_MAX_SIZE);
+3 -3
View File
@@ -106,9 +106,9 @@ protected:
class ErrorDialog : public MsgDialog
{
public:
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;
+3 -3
View File
@@ -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()
+41 -7
View File
@@ -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
View File
@@ -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;
+6 -6
View File
@@ -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;
@@ -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);
+28
View File
@@ -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;
}
}
@@ -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" ||
+9
View File
@@ -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; }
+30 -8
View File
@@ -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);
@@ -3337,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);
@@ -4375,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());
@@ -4412,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);
@@ -4421,8 +4445,6 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
//this may be happened after machine changed
void PartPlateList::reset_size(int width, int depth, int height, bool reload_objects, bool update_shapes)
{
Vec3d origin1, origin2;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":before size: plate_width %1%, plate_depth %2%, plate_height %3%") % m_plate_width % m_plate_depth % m_plate_height;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":after size: plate_width %1%, plate_depth %2%, plate_height %3%") % width % depth % height;
if ((m_plate_width != width) || (m_plate_depth != depth) || (m_plate_height != height))
+3
View File
@@ -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; }
+2 -88
View File
@@ -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"
@@ -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
-1
View File
@@ -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();
+87 -64
View File
@@ -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)
@@ -5631,6 +5640,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&);
@@ -5779,6 +5789,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);
@@ -5793,7 +5805,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");
@@ -6575,9 +6587,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);
}
@@ -7876,7 +7888,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
@@ -7895,7 +7907,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) {
@@ -8803,7 +8815,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();
@@ -8832,7 +8844,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();
}
@@ -9513,7 +9525,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);
@@ -9533,13 +9545,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;
}
@@ -9547,12 +9559,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
@@ -11186,18 +11198,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)
@@ -11213,10 +11230,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)
@@ -11228,16 +11242,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) {
@@ -11246,9 +11266,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();
@@ -11293,13 +11321,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);
}
@@ -12114,7 +12137,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;
}
@@ -12714,7 +12737,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();
@@ -12824,7 +12847,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;
@@ -13103,7 +13126,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);
@@ -13252,7 +13275,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();
@@ -13366,7 +13389,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;
@@ -13848,7 +13871,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));
@@ -14330,7 +14353,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)
@@ -14369,7 +14392,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;
@@ -14645,7 +14668,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"))
@@ -14726,7 +14749,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;
@@ -14787,7 +14810,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;
@@ -14871,7 +14894,7 @@ 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;
@@ -14940,7 +14963,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;
@@ -15008,7 +15031,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;
@@ -15144,7 +15167,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.")));
@@ -15831,7 +15854,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;
}
@@ -16060,7 +16083,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);
}
}
@@ -17874,7 +17897,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()));
@@ -17906,7 +17929,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)
@@ -17921,7 +17944,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)
@@ -18815,7 +18838,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);
}
}
+2 -2
View File
@@ -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);
@@ -1026,4 +1026,4 @@ wxArrayString get_all_camera_view_type();
} // namespace GUI
} // namespace Slic3r
#endif
#endif
+1 -34
View File
@@ -15,39 +15,6 @@ 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 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
@@ -129,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);
}
}
+31 -2
View File
@@ -79,7 +79,7 @@ public:
Bind(wxEVT_LEFT_DOWN, &WikiLabel::OnLeftDown, this);
}
void SetLabel(const wxString& label)
void SetLabel(const wxString& label) override
{
m_label = label;
m_last_wrap_width = -1; // force re-wrap
@@ -1135,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");
@@ -1740,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));
@@ -2101,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);
+4 -1
View File
@@ -865,6 +865,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
clr_picker = new wxBitmapButton(parent, wxID_ANY, {}, wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), wxBU_EXACTFIT | wxBU_AUTODRAW | wxBORDER_NONE);
clr_picker->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
clr_picker->SetToolTip(_L("Click to select filament color"));
#ifdef __WXGTK__
RemoveButtonBorder(clr_picker);
#endif
clr_picker->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
// Check if it's an official filament
auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data());
@@ -1039,7 +1042,7 @@ bool PlaterPresetComboBox::switch_to_tab()
//BBS Select NoteBook Tab params
if (tab->GetParent() == wxGetApp().params_panel())
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
else {
wxGetApp().params_dialog()->Popup();
tab->OnActivate();
+1 -1
View File
@@ -39,7 +39,7 @@ public:
PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr);
~PresetComboBox();
enum LabelItemType {
enum LabelItemType : std::size_t {
LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01,
LABEL_ITEM_PRINTER_MODELS,
LABEL_ITEM_DISABLED,
+2 -2
View File
@@ -163,7 +163,7 @@ public:
BedType bedType() const { return m_BedType; }
virtual void init() override;
virtual std::map<std::string, std::string> extendedInfo() const
virtual std::map<std::string, std::string> extendedInfo() const override
{
return {{"bedType", std::to_string(static_cast<int>(m_BedType))},
{"timeLapse", std::to_string(m_timeLapse)},
@@ -200,7 +200,7 @@ public:
PrintHost* printhost);
virtual void init() override;
virtual std::map<std::string, std::string> extendedInfo() const;
virtual std::map<std::string, std::string> extendedInfo() const override;
private:
static constexpr const char* CONFIG_KEY_ENABLESELFTEST = "crealityprint_enable_self_test";
+51 -10
View File
@@ -74,7 +74,18 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
Fit();
}
ProjectPanel::~ProjectPanel() {}
ProjectPanel::~ProjectPanel()
{
shutdown();
}
void ProjectPanel::shutdown()
{
m_reload_cancel_token->store(true, std::memory_order_release);
if (m_reload_task && m_reload_task->joinable())
m_reload_task->join();
m_reload_task.reset();
}
// Helper to convert newlines to <br>
static std::string convert_newlines_to_br(const std::string& text) {
@@ -101,7 +112,17 @@ void ProjectPanel::onWebNavigating(wxWebViewEvent& evt)
void ProjectPanel::on_reload(wxCommandEvent& evt)
{
boost::thread reload = boost::thread([this] {
if (wxTheApp == nullptr || wxGetApp().is_closing() ||
m_reload_cancel_token->load(std::memory_order_acquire))
return;
if (m_reload_task && m_reload_task->joinable())
m_reload_task->join();
const auto cancel_token = m_reload_cancel_token;
m_reload_task = std::make_unique<boost::thread>([this, cancel_token] {
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
return;
std::string update_type;
std::string license;
std::string model_name;
@@ -115,6 +136,9 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
std::map<std::string, std::vector<json>> files;
if (wxGetApp().plater() == nullptr)
return;
Model model = wxGetApp().plater()->model();
auto model_info = model.model_info;
@@ -156,7 +180,14 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str());
if (!file_path.empty()) {
files = Reload(file_path);
wxGetApp().CallAfter([this, file_path, files] { m_auxiliary->Reload(file_path, files); });
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
return;
wxGetApp().CallAfter([this, cancel_token, file_path, files] {
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
return;
m_auxiliary->Reload(file_path, files);
});
} else {
clear_model_info();
return;
@@ -215,15 +246,18 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
json m_Res = json::object();
m_Res["command"] = "show_3mf_info";
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
m_Res["model"] = j;
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
if (m_web_init_completed) {
wxGetApp().CallAfter([this, strJS] {
if (m_web_init_completed.load(std::memory_order_acquire) &&
!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
wxGetApp().CallAfter([this, cancel_token, strJS] {
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
return;
RunScript(strJS.ToStdString());
});
});
}
});
}
@@ -264,7 +298,7 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
}
}
else if (strCmd == "request_3mf_info") {
m_web_init_completed = true;
m_web_init_completed.store(true, std::memory_order_release);
}
else if (strCmd == "edit_project_info") {
show_info_editor(true);
@@ -307,13 +341,20 @@ void ProjectPanel::update_model_data()
void ProjectPanel::clear_model_info()
{
if (wxTheApp == nullptr || wxGetApp().is_closing() ||
m_reload_cancel_token->load(std::memory_order_acquire))
return;
json m_Res = json::object();
m_Res["command"] = "clear_3mf_info";
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
wxGetApp().CallAfter([this, strJS] {
const auto cancel_token = m_reload_cancel_token;
wxGetApp().CallAfter([this, cancel_token, strJS] {
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
return;
RunScript(strJS.ToStdString());
});
}
+8 -2
View File
@@ -26,9 +26,11 @@
#include "nlohmann/json.hpp"
#include "slic3r/Utils/json_diff.hpp"
#include <atomic>
#include <map>
#include <vector>
#include <memory>
#include <boost/thread.hpp>
#include "Event.hpp"
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
@@ -60,14 +62,17 @@ struct project_file{
class ProjectPanel : public wxPanel
{
private:
bool m_web_init_completed = {false};
std::atomic<bool> m_web_init_completed{false};
bool m_reload_already = {false};
std::shared_ptr<std::atomic<bool>> m_reload_cancel_token{std::make_shared<std::atomic<bool>>(false)};
std::unique_ptr<boost::thread> m_reload_task;
wxWebView* m_browser = {nullptr};
AuxiliaryPanel* m_auxiliary{nullptr};
wxString m_project_home_url;
wxString m_root_dir;
static inline int m_sequence_id = 8000;
static inline std::atomic<int> m_sequence_id{8000};
void show_info_editor(bool show);
@@ -75,6 +80,7 @@ private:
public:
ProjectPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
~ProjectPanel();
void shutdown();
void onWebNavigating(wxWebViewEvent& evt);
+7 -2
View File
@@ -1991,7 +1991,7 @@ void InputIpAddressDialog::workerThreadFunc(std::string str_ip, std::string str_
if (w.expired()) return;
if (m_obj) {
m_obj->set_user_access_code(str_access_code);
m_obj->set_access_code(str_access_code);
wxGetApp().getDeviceManager()->set_selected_machine(m_obj->get_dev_id());
}
@@ -2055,6 +2055,11 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
{
auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
if (str_access_code.empty()) {
str_access_code = "88888888";
}
auto str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both);
auto str_sn = m_input_sn->GetTextCtrl()->GetValue().Strip(wxString::both);
bool invalid_access_code = true;
@@ -2062,7 +2067,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
for (char c : str_access_code) {
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
invalid_access_code = false;
return;
break;
}
}
+50 -6
View File
@@ -112,12 +112,56 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
sizer->Add(m_radio_group, 0, wxEXPAND | wxTOP | wxLEFT, BORDER_W);
if (parent->m_mode == comDevelop) {
m_detach_checkbox = new wxCheckBox(parent, wxID_ANY, _L("Detach from parent"));
sizer->Add(m_detach_checkbox, 0, wxALIGN_LEFT | wxALL, BORDER_W);
// Set initial state (unchecked by default)
m_detach_checkbox->SetValue(m_detach);
// Bind the checkbox event to update the detach state for this item
m_detach_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_detach = m_detach_checkbox->GetValue(); });
// A new user copy of a system preset inherits from the selected system preset.
const std::string parent_name = sel_preset.is_system ? sel_preset.name : sel_preset.inherits();
const bool can_detach = !parent_name.empty();
wxBoxSizer *detach_sizer = new wxBoxSizer(wxHORIZONTAL);
auto detach_tooltip = _L("Copies all inherited values from the parent into this preset and removes the parent relationship. Presets compatible only with the parent may become unsupported.");
auto detach_checkbox = new ::CheckBox(parent);
detach_checkbox->SetToolTip(detach_tooltip);
auto detach_label = new wxStaticText(parent, wxID_ANY, _L("Detach from parent"));
detach_label->SetFont(::Label::Body_14);
detach_label->SetToolTip(detach_tooltip);
detach_sizer->Add(detach_checkbox, 0, wxALIGN_LEFT | wxLEFT, BORDER_W);
detach_sizer->Add(detach_label , 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, FromDIP(5));
sizer->Add(detach_sizer, 0, wxEXPAND | wxTOP, BORDER_W);
sizer->AddSpacer(FromDIP(5));
const wxString parent_text = can_detach ? from_u8(parent_name) : _L("Unique preset");
auto parent_label = new wxStaticText(parent, wxID_ANY, parent_text);
parent_label->SetFont(::Label::Body_12);
parent_label->SetForegroundColour(wxColour("#6B6B6B"));
parent_label->SetToolTip(can_detach ? _L("Parent preset") : _L("This preset does not inherit from another preset."));
sizer->Add(parent_label, 0, wxEXPAND | wxLEFT, BORDER_W + FromDIP(24));
sizer->AddSpacer(FromDIP(5));
if (!can_detach) {
detach_checkbox->Disable();
detach_label->SetForegroundColour(wxColour("#6B6B6B"));
}
else {
// Set initial state (unchecked by default)
detach_checkbox->SetValue(m_detach);
// Bind the checkbox event to update the detach state for this item
detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent&) { m_detach = detach_checkbox->GetValue(); });
detach_label->SetForegroundColour(wxColour("#363636"));
auto on_toggle = [this, detach_checkbox]() {
detach_checkbox->SetValue(!detach_checkbox->GetValue());
wxCommandEvent ev(wxEVT_TOGGLEBUTTON, detach_checkbox->GetId());
ev.SetEventObject(detach_checkbox);
detach_checkbox->GetEventHandler()->ProcessEvent(ev);
};
detach_label->Bind(wxEVT_LEFT_DOWN, [on_toggle](wxMouseEvent& e) {if(!e.LeftDClick()) on_toggle();});
detach_label->Bind(wxEVT_LEFT_DCLICK, [on_toggle](wxMouseEvent& e) {on_toggle();});
}
}
m_radio_group->Bind(wxEVT_COMMAND_RADIOBOX_SELECTED, [this](wxCommandEvent &e) {
-1
View File
@@ -75,7 +75,6 @@ class SavePresetDialog : public DPIDialog
bool m_save_to_project {false};
RadioGroup* m_radio_group; // ORCA
bool m_detach{false};
wxCheckBox* m_detach_checkbox{nullptr};
void update();
};
+9 -10
View File
@@ -1088,8 +1088,8 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
}
}
relayout_nozzle_cards();
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
updata_thumbnail_data_after_connected_printer();
}
}
@@ -3629,7 +3629,7 @@ void SelectMachineDialog::on_send_print()
BOOST_LOG_TRIVIAL(error) << "build_nozzle_info errors";
}
m_print_job->sdcard_state = obj_->GetStorage()->get_sdcard_state();
m_print_job->sdcard_state = obj_->GetStorage()->get_sdcard_state();
m_print_job->has_sdcard = wxGetApp().app_config->get("allow_abnormal_storage") == "true"
? (m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL
|| m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL)
@@ -3868,12 +3868,11 @@ _compare_obj_names(MachineObject* obj1, MachineObject* obj2)
}
/*******************************************************************
*@note _collect_machine_list
*@param dev_manager -- the device manager
*@param sorted_machine_objs -- return the sorted machine objects
*@param best_one -- return the best one
*/
/*******************************************************************/
* @note _collect_machine_list
* @param dev_manager -- the device manager
* @param sorted_machine_objs -- return the sorted machine objects
* @param best_one -- return the best one
*******************************************************************/
static void
_collect_sorted_machines(Slic3r::DeviceManager* dev_manager,
std::vector<MachineObject*>& sorted_machine_objs)
@@ -3913,7 +3912,7 @@ _collect_sorted_machines(Slic3r::DeviceManager* dev_manager,
};
// collect from user machine list
const auto& user_machine_list = dev_manager->get_my_machine_list();// user machine list
const auto& user_machine_list = dev_manager->get_my_machine_list(dev_manager->get_current_printer_agent_id());// user machine list
for (const auto& elem : user_machine_list)
{
MachineObject* mobj = elem.second;
+1 -1
View File
@@ -522,7 +522,7 @@ public:
bool is_timeout();
int update_print_required_data(Slic3r::DynamicPrintConfig config, Slic3r::Model model, Slic3r::PlateDataPtrs plate_data_list, std::string file_name, std::string file_path);
void set_print_type(PrintFromType type) {m_print_type = type;};
bool Show(bool show);
bool Show(bool show) override;
void show_init();
bool do_ams_mapping(MachineObject *obj_,bool use_ams);
bool get_ams_mapping_result(std::string& mapping_array_str, std::string& mapping_array_str2, std::string& ams_mapping_info) const;
+6 -2
View File
@@ -501,6 +501,7 @@ void SelectMachinePopup::update_other_devices()
DeviceManager* dev = wxGetApp().getDeviceManager();
if (!dev) return;
m_free_machine_list = dev->get_local_machinelist();
const std::string current_agent_id = dev->get_current_printer_agent_id();
BOOST_LOG_TRIVIAL(trace) << "SelectMachinePopup update_other_devices start";
this->Freeze();
@@ -512,6 +513,10 @@ void SelectMachinePopup::update_other_devices()
/* do not show printer bind state is empty */
if (!mobj->is_avaliable()) continue;
/* do not show devices discovered/bound by a different printer agent */
if (mobj->printer_agent_id != current_agent_id)
continue;
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider()) && !mobj->is_lan_mode_printer())
continue;
@@ -634,7 +639,7 @@ void SelectMachinePopup::update_user_devices()
}
m_bind_machine_list.clear();
m_bind_machine_list = dev->get_my_machine_list();
m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
//sort list
std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
@@ -704,7 +709,6 @@ void SelectMachinePopup::update_user_devices()
}
mobj->set_access_code("");
mobj->erase_user_access_code();
}
if (GUI::wxGetApp().plater())
+2 -15
View File
@@ -1270,9 +1270,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
} else {
if (v.is_wipe_tower) {//in world cs
int plate_idx = v.object_idx() - 1000;
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->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])));
Vec3d tower_size = v.bounding_box().size();
const Polygons bed_polys{wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_shared_printable_polygon()};
Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position();
Vec3d actual_displacement = displacement;
bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower);
@@ -1287,18 +1285,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
BoundingBoxf3 tower_bbox = v.bounding_box();
tower_bbox.translate(actual_displacement + tower_origin);
BoundingBox tower_bbox2d = BoundingBox(scaled(Vec2f(tower_bbox.min[0], tower_bbox.min[1])), scaled(Vec2f(tower_bbox.max[0], tower_bbox.max[1])));
Vec2f offset = WipeTower::move_box_inside_box(tower_bbox2d, plate_bbox2d,scaled(margin));
//if (tower_origin(0) + actual_displacement(0) - margin < plate_bbox.min(0)) {
// actual_displacement(0) = plate_bbox.min(0) - tower_origin(0) + margin;
//} else if (tower_origin(0) + actual_displacement(0) + tower_size(0) + margin > plate_bbox.max(0)) {
// actual_displacement(0) = plate_bbox.max(0) - tower_origin(0) - tower_size(0) - margin;
//}
//if (tower_origin(1) + actual_displacement(1) - margin < plate_bbox.min(1)) {
// actual_displacement(1) = plate_bbox.min(1) - tower_origin(1) + margin;
//} else if (tower_origin(1) + actual_displacement(1) + tower_size(1) + margin > plate_bbox.max(1)) {
// actual_displacement(1) = plate_bbox.max(1) - tower_origin(1) - tower_size(1) - margin;
//}
const Vec2f offset = WipeTower::move_box_inside_polygon(tower_bbox2d, bed_polys, scaled(margin));
actual_displacement += Vec3d(offset[0], offset[1],0);
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement);
}
+1 -1
View File
@@ -180,7 +180,7 @@ public:
SendToPrinterDialog(Plater *plater = nullptr);
~SendToPrinterDialog();
bool Show(bool show);
bool Show(bool show) override;
bool is_timeout();
void on_rename_click(wxCommandEvent& event);
void on_rename_enter();
+2 -2
View File
@@ -1218,8 +1218,8 @@ void SyncAmsInfoDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &resul
iter++;
}
}
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
updata_thumbnail_data_after_connected_printer();
}
}
+1 -1
View File
@@ -371,7 +371,7 @@ public:
};
FinishSyncAmsDialog(InputInfo &input_info);
~FinishSyncAmsDialog() override;
void deal_ok();
void deal_ok() override;
void update_info(InputInfo& info);
bool Layout() override;
+2
View File
@@ -21,7 +21,9 @@
#ifdef _WIN32
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <psapi.h>
#endif /* _WIN32 */
+115 -47
View File
@@ -34,6 +34,7 @@
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
#include "Plater.hpp"
@@ -1746,6 +1747,13 @@ void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
if (line) line->toggle_visible = toggle;
};
void Tab::set_option_label(const std::string &opt_key, const wxString &label, int opt_index)
{
if (!m_active_page) return;
Line *line = m_active_page->get_line(opt_key, opt_index);
if (line) line->set_label(label);
}
// To be called by custom widgets, load a value into a config,
// update the preset selection boxes (the dirty flags)
// If value is saved before calling this function, put saved_value = true,
@@ -2136,43 +2144,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
m_last_sparse_infill_rotate_template_value = m_config->opt_string("sparse_infill_rotate_template");
}
if(opt_key=="layer_height"){
auto min_layer_height_from_nozzle=m_preset_bundle->full_config().option<ConfigOptionFloats>("min_layer_height")->values;
auto max_layer_height_from_nozzle=m_preset_bundle->full_config().option<ConfigOptionFloats>("max_layer_height")->values;
auto layer_height_floor = *std::min_element(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end());
auto layer_height_ceil = *std::max_element(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end());
const auto lh = m_config->opt_float("layer_height");
bool exceed_minimum_flag = lh < layer_height_floor;
bool exceed_maximum_flag = lh > layer_height_ceil;
if (exceed_maximum_flag || exceed_minimum_flag) {
if (lh < EPSILON) {
auto msg_text = _(L("Layer height is too small.\nIt will set to min_layer_height\n"));
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK);
dialog.SetButtonLabel(wxID_OK, _L("OK"));
dialog.ShowModal();
auto new_conf = *m_config;
new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor));
m_config_manipulation.apply(m_config, &new_conf);
} else {
wxString msg_text = _(L("Layer height exceeds the limit in Printer Settings -> Extruder -> Layer height limits, "
"this may cause printing quality issues."));
msg_text += "\n\n" + _(L("Adjust to the set range automatically?\n"));
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO);
dialog.SetButtonLabel(wxID_YES, _L("Adjust"));
dialog.SetButtonLabel(wxID_NO, _L("Ignore"));
auto answer = dialog.ShowModal();
auto new_conf = *m_config;
if (answer == wxID_YES) {
if (exceed_maximum_flag)
new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_ceil));
if (exceed_minimum_flag)
new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor));
m_config_manipulation.apply(m_config, &new_conf);
}
}
if (opt_key == "layer_height") {
if (m_config_manipulation.check_layer_height(m_config))
wxGetApp().plater()->update();
}
}
string opt_key_without_idx = opt_key.substr(0, opt_key.find('#'));
@@ -2824,6 +2798,7 @@ void TabPrint::build()
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
@@ -3080,6 +3055,7 @@ void TabPrint::build()
optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims");
optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle");
optgroup->append_single_option_line("brim_ears_detection_length", "others_settings_brim#ear-detection-radius");
optgroup->append_single_option_line("brim_ears_outer_only", "others_settings_brim#brim-ears-outer-only");
optgroup = page->new_optgroup(L("Special mode"), L"param_special");
optgroup->append_single_option_line("slicing_mode", "others_settings_special_mode#slicing-mode");
@@ -4043,13 +4019,12 @@ void TabFilament::add_filament_overrides_page()
const int extruder_idx = 0; // #ys_FIXME
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
auto append_retraction_option = [this, retraction_optgroup](const std::string& opt_key, int opt_index)
auto append_retraction_option = [this](ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index)
{
Line line {"",""};
line = retraction_optgroup->create_single_option_line(retraction_optgroup->get_option(opt_key, opt_index));
line = optgroup->create_single_option_line(optgroup->get_option(opt_key, opt_index));
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(retraction_optgroup), opt_key, opt_index](wxWindow* parent) {
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
auto check_box = new ::CheckBox(parent); // ORCA modernize checkboxes
check_box->Bind(wxEVT_TOGGLEBUTTON, [this, optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
const bool is_checked = evt.IsChecked();
@@ -4076,9 +4051,10 @@ void TabFilament::add_filament_overrides_page()
return check_box;
};
retraction_optgroup->append_line(line);
optgroup->append_line(line);
};
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
for (const std::string opt_key : { "filament_retraction_length",
"filament_z_hop",
"filament_z_hop_types",
@@ -4102,7 +4078,13 @@ void TabFilament::add_filament_overrides_page()
//SoftFever
// "filament_seam_gap"
})
append_retraction_option(opt_key, extruder_idx);
append_retraction_option(retraction_optgroup, opt_key, extruder_idx);
ConfigOptionsGroupShp toolchange_optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction_material_change");
for (const std::string opt_key : { "filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange"
})
append_retraction_option(toolchange_optgroup, opt_key, extruder_idx);
ConfigOptionsGroupShp ironing_optgroup = page->new_optgroup(L("Ironing"), L"param_ironing");
auto append_ironing_option = [this, ironing_optgroup](const std::string& opt_key, int opt_index)
@@ -4217,6 +4199,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
"filament_retraction_speed",
"filament_deretraction_speed",
"filament_retract_restart_extra",
"filament_retract_length_toolchange",
"filament_retract_restart_extra_toolchange",
"filament_retraction_minimum_travel",
"filament_retract_when_changing_layer",
"filament_wipe",
@@ -4247,7 +4231,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
is_checked &= !dynamic_cast<ConfigOptionVectorBase*>(m_config->option(opt_key))->is_nil(extruder_idx);
m_overrides_options[opt_key]->SetValue(is_checked);
Field* field = optgroup->get_fieldc(opt_key, 0);
// the toolchange overrides live in their own optgroup, so search the whole page
Field* field = page->get_field(opt_key, 0);
if (field == nullptr) continue;
if (opt_key == "filament_long_retractions_when_cut") {
@@ -5053,8 +5038,43 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("gcode_skip_config_block", "printer_basic_information_advanced#skip-g-code-config-block");
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
// "Printer Agent" dropdown - printer_agent is a coString; gui_type routes it to
// PrinterAgentChoice instead of a TextCtrl. Rows and values come from the live agent
// registry, and the value is stored as the agent-id string.
if (wxGetApp().getAgent() != nullptr)
{
auto registered_printer_agents = NetworkAgentFactory::get_registered_printer_agents();
if (!registered_printer_agents.empty())
{
ConfigOptionDef def;
def.type = coString;
def.gui_type = ConfigOptionDef::GUIType::printer_agent_select;
def.width = 3 * Field::def_width_wider() / 2;
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;
// Create the field without get_option() so it is not registered in m_opt_map.
// ConfigOptionsGroup handles printer_agent before the generic mapped write path.
Line agent_line = optgroup->create_single_option_line(Option(def, "printer_agent"));
optgroup->append_line(agent_line);
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
choice->set_value(m_config->opt_string("printer_agent"), false);
}
// Register by hand so the UnsavedChanges dialog can render a row for it.
wxGetApp().sidebar().get_searcher().add_key("printer_agent", m_type, optgroup->title,
optgroup->config_category());
}
}
optgroup->append_single_option_line("use_3mf");
optgroup->append_single_option_line("scan_first_layer" , "printer_basic_information_advanced#scan-first-layer");
optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery");
@@ -5698,6 +5718,7 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower");
optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming");
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
optgroup->append_single_option_line("wait_for_temp_on_wipe_tower", "printer_multimaterial_wipe_tower#wait-for-temperature-on-wipe-tower");
// Orca-Belt: belt printers replace the classic wipe tower with an
// auto-generated purge prism; this is its enable (gated to belt printers
@@ -6018,6 +6039,16 @@ void TabPrinter::reload_config()
// so update it implicitly
if (m_active_page && m_active_page->title() == "Multimaterial")
m_active_page->set_value("extruders_count", int(m_extruders_count));
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
{
const std::string selected_agent = m_config->opt_string("printer_agent");
choice->set_value(selected_agent, false);
}
}
}
void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
@@ -6028,6 +6059,16 @@ void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
// so update it implicitly
if (m_active_page && m_active_page->title() == "Multimaterial")
m_active_page->set_value("extruders_count", int(m_extruders_count));
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
if (Field* agent_field = get_field("printer_agent"))
{
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
{
const std::string selected_agent = m_config->opt_string("printer_agent");
choice->set_value(selected_agent, false);
}
}
}
void TabPrinter::clear_pages()
@@ -6273,6 +6314,7 @@ void TabPrinter::toggle_options()
// so the option is irrelevant there.
const size_t extruders_count = m_config->option<ConfigOptionFloats>("nozzle_diameter")->size();
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
toggle_option("wait_for_temp_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
}
wxString extruder_number;
long val = 1;
@@ -6637,7 +6679,7 @@ void Tab::load_current_preset()
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog";
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), wxString(), tab, tab->title(), bmp_name);
}
else
#endif
@@ -8064,6 +8106,24 @@ bool TabPrinter::apply_extruder_cnt_from_cache()
return false;
}
void TabPrinter::refresh_printer_agent_dropdown() const
{
auto* choice = dynamic_cast<PrinterAgentChoice*>(get_field("printer_agent"));
if (!choice || !choice->getWindow())
return;
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
if (agents.empty())
return;
// why: rows live on PrinterAgentChoice now; rebuild them from the live registry and re-select the stored id.
const std::string selected_agent = wxGetApp().preset_bundle->printers.get_edited_preset()
.config.opt_string("printer_agent");
choice->reload_rows();
choice->set_value(selected_agent, false);
this->GetParent()->Layout();
}
bool Tab::validate_custom_gcodes()
{
if (m_type != Preset::TYPE_FILAMENT &&
@@ -8698,8 +8758,12 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#ifdef __WXMSW__
// BBS: fix field control position
wxTheApp->CallAfter([this]() {
for (auto group : m_optgroups) {
wxTheApp->CallAfter([wp = std::weak_ptr<Page>(shared_from_this())]() {
auto page = wp.lock();
if (!page)
return;
for (auto group : page->m_optgroups) {
if (group->custom_ctrl)
group->custom_ctrl->fixup_items_positions();
}
@@ -9155,11 +9219,15 @@ ConfigManipulation Tab::get_config_manipulation()
return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index);
};
auto cb_set_option_label = [this](const t_config_option_key &opt_key, const wxString &label, int opt_index) {
return set_option_label(opt_key, label, opt_index >= 0 ? opt_index + 256 : opt_index);
};
auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) {
return on_value_change(opt_key, value);
};
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this);
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this, cb_set_option_label);
}
+4 -2
View File
@@ -402,6 +402,7 @@ public:
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
void toggle_option(const std::string &opt_key, bool toggle, int opt_index = -1);
void toggle_line(const std::string &opt_key, bool toggle, int opt_index = -1); // BBS: hide some line
void set_option_label(const std::string &opt_key, const wxString &label, int opt_index = -1);
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
bool current_preset_is_dirty() const;
bool saved_preset_is_dirty() const;
@@ -514,13 +515,13 @@ public:
bool has_key(std::string const &key);
protected:
virtual void activate_selected_page(std::function<void()> throw_if_canceled);
virtual void activate_selected_page(std::function<void()> throw_if_canceled) override;
virtual void on_value_change(const std::string& opt_key, const boost::any& value) override;
virtual void notify_changed(ObjectBase * object) = 0;
virtual void reload_config();
virtual void reload_config() override;
virtual void update_custom_dirty(std::vector<std::string> &dirty_options, std::vector<std::string> &nonsys_options) override;
@@ -681,6 +682,7 @@ public:
wxSizer* create_bed_shape_widget(wxWindow* parent);
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
bool apply_extruder_cnt_from_cache();
void refresh_printer_agent_dropdown() const;
};
class TabSLAMaterial : public Tab
+1 -1
View File
@@ -40,7 +40,7 @@ public:
void SetBitmap(ScalableBitmap &bitmap);
bool Enable(bool enable = true);
bool Enable(bool enable = true) override;
void Rescale();
+5 -23
View File
@@ -108,7 +108,7 @@ public:
// by this control) and show it immediately.
bool ShowNewPage(wxWindow * page)
{
return AddPage(page, wxString(), ""/*true *//* select it */);
return AddPage(page, wxString());
}
// Set effect to use for showing/hiding pages.
@@ -139,14 +139,13 @@ public:
// Implement base class pure virtual methods.
// adds a new page to the control
bool AddPage(wxWindow* page,
const wxString& text,
const std::string& bmp_name,
bool bSelect = false)
bool bSelect = false,
int imageId = NO_IMAGE) override
{
DoInvalidateBestSize();
return InsertNewPage(GetPageCount(), page, text, bmp_name, bSelect);
return InsertPage(GetPageCount(), page, text, bSelect, imageId);
}
//// Page management
@@ -167,24 +166,7 @@ public:
return true;
}
bool InsertNewPage(size_t n,
wxWindow * page,
const wxString & text,
const std::string& bmp_name = "",
bool bSelect = false)
{
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
return false;
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
if (bSelect)
SetSelection(n);
return true;
}
bool RemovePage(size_t n)
bool RemovePage(size_t n) override
{
if (!wxBookCtrlBase::RemovePage(n))
return false;
+1 -1
View File
@@ -343,7 +343,7 @@ public:
UnsavedChangesDialog(const wxString &caption, const wxString &header, DynamicConfig *config, int from, int to, bool left_to_right, NozzleVolumeType nozzle);
~UnsavedChangesDialog() override = default;
int ShowModal();
int ShowModal() override;
void build(Preset::Type type, PresetCollection *dependent_presets, const std::string &new_selected_preset, const wxString &header = "");
void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header);
+369 -91
View File
@@ -1,7 +1,9 @@
#include "WebGuideDialog.hpp"
#include "ConfigWizard.hpp"
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/iostreams/detail/select.hpp>
#include <boost/log/trivial.hpp>
@@ -9,7 +11,9 @@
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/PresetCacheFormat.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r_version.h"
@@ -41,8 +45,6 @@ using namespace nlohmann;
namespace Slic3r { namespace GUI {
json m_ProfileJson;
static wxString update_custom_filaments()
{
json m_Res = json::object();
@@ -190,12 +192,10 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
GuideFrame::~GuideFrame()
{
m_destroy = true;
if (m_load_task && m_load_task->joinable()) {
*m_cancel_token = true; // stop the loading thread and any queued CallAfter lambdas before join
if (m_load_task && m_load_task->joinable())
m_load_task->join();
delete m_load_task;
m_load_task = nullptr;
}
m_load_task.reset();
if (m_browser) {
delete m_browser;
m_browser = nullptr;
@@ -301,15 +301,71 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
/**
* Callback invoked when a navigation request was accepted
*/
// The empty shape every profile-loading path starts from or falls back to.
void GuideFrame::reset_profile_json()
{
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
}
void GuideFrame::init_guide_paths()
{
m_ProfileJson = json::parse("{}");
reset_profile_json();
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
orca_bundle_rsrc = true;
if (boost::filesystem::exists(vendor_dir)) {
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
if (!boost::filesystem::is_directory(entry) &&
boost::iequals(entry.path().extension().string(), ".json") &&
!boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
orca_bundle_rsrc = false;
break;
}
}
}
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
}
void GuideFrame::on_profile_loaded()
{
// Must be called on the main thread.
SaveProfileData();
const std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents:\n" << strAll;
json res;
res["command"] = "userguide_profile_load_finish";
res["sequence_id"] = "10001";
RunScript(wxString::Format("HandleStudio(%s)", res.dump(-1, ' ', true)));
}
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
{
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
if (!bFirstComplete) {
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
//LoadProfileThread.detach();
bFirstComplete = true;
try {
init_guide_paths();
if (BuildProfileDataFromPresetBundle()) {
if (!*m_cancel_token)
on_profile_loaded();
} else {
// Presets not yet in memory — delegate to background thread.
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", init error: " << e.what();
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
}
}
m_browser->Show();
@@ -762,11 +818,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
bool check_unsaved_preset_changes = false;
std::vector<std::string> install_bundles;
std::vector<std::string> remove_bundles;
const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
for (const auto &it : enabled_vendors) {
if (it.second.size() > 0) {
auto vendor_file = vendor_dir/(it.first + ".json");
if (!fs::exists(vendor_file)) {
if (!is_vendor_installed(it.first)) {
install_bundles.emplace_back(it.first);
}
}
@@ -777,8 +831,7 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
if (it.second.size() > 0) {
if (enabled_vendors.find(it.first) != enabled_vendors.end())
continue;
auto vendor_file = vendor_dir/(it.first + ".json");
if (fs::exists(vendor_file)) {
if (is_vendor_installed(it.first)) {
remove_bundles.emplace_back(it.first);
}
}
@@ -1127,99 +1180,324 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
return status;
}
int GuideFrame::LoadProfileData()
bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors)
{
try {
m_ProfileJson = json::parse("{}");
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
// Models from vendor profiles
for (const auto& [vendor_id, vp] : bundle.vendors) {
for (const auto& model : vp.models) {
std::string nozzle_str;
for (const auto& v : model.variants) {
if (!nozzle_str.empty()) nozzle_str += ";";
nozzle_str += v.name;
}
const std::string materials_str = boost::algorithm::join(model.default_materials, ";");
boost::filesystem::path cover_path =
(boost::filesystem::path(resources_dir()) / "profiles" / vp.id / (model.id + "_cover.png"))
.make_preferred();
if (!boost::filesystem::exists(cover_path))
cover_path =
(boost::filesystem::path(resources_dir()) / "web/image/printer" / (model.id + "_cover.png"))
.make_preferred();
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
// Orca: add custom as default
// Orca: add json logic for vendor bundle
orca_bundle_rsrc = true;
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json") && !boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
orca_bundle_rsrc = false;
break;
json entry;
entry["model"] = model.id;
entry["name"] = model.name;
entry["vendor"] = vp.id;
entry["nozzle_diameter"] = nozzle_str;
entry["materials"] = materials_str;
entry["cover"] = cover_path.string();
entry["nozzle_selected"] = "";
entry["sub_path"] = "";
m_ProfileJson["model"].push_back(entry);
}
}
// load the default filament library first
std::set<std::string> loaded_vendors;
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
if (boost::filesystem::exists(vendor_dir / filament_library_name)) {
m_OrcaFilaLibPath = (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
} else {
m_OrcaFilaLibPath = (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
}
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
// Machine map: preset name -> {model, nozzle variant}
for (const Preset& p : bundle.printers()) {
if (!p.is_system || !p.vendor) continue;
const auto* printer_model = p.config.option<ConfigOptionString>("printer_model");
const auto* printer_variant = p.config.option<ConfigOptionString>("printer_variant");
if (!printer_model || printer_model->value.empty() || !printer_variant) continue;
//load custom bundle from user data path
boost::filesystem::directory_iterator endIter;
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
if (!boost::filesystem::is_directory(*iter)) {
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
strVendor = strVendor.AfterLast('\\');
strVendor = strVendor.AfterLast('/');
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
if(strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
continue;
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (m_destroy)
return 0;
json mach;
mach["model"] = printer_model->value;
mach["nozzle"] = printer_variant->value;
m_ProfileJson["machine"][p.name] = mach;
}
boost::filesystem::directory_iterator others_endIter;
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
if (!boost::filesystem::is_directory(*iter)) {
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
strVendor = strVendor.AfterLast('\\');
strVendor = strVendor.AfterLast('/');
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
continue;
// Filament map from system filament presets (vendor/type already resolved in config)
const json& machines = m_ProfileJson["machine"];
for (const Preset& p : bundle.filaments()) {
if (!p.is_system || !p.vendor) continue;
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
const auto* fila_type = p.config.option<ConfigOptionStrings>("filament_type");
const auto* compat_printers = p.config.option<ConfigOptionStrings>("compatible_printers");
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
std::string vendor = (fila_vendor && !fila_vendor->values.empty()) ? fila_vendor->values[0] : "";
std::string type = (fila_type && !fila_type->values.empty()) ? fila_type->values[0] : "";
std::string model_list;
if (compat_printers) {
for (const std::string& pname : compat_printers->values) {
auto it = machines.find(pname);
if (it != machines.end()) {
const std::string m = (*it)["model"];
const std::string n = (*it)["nozzle"];
model_list += "[" + m + "++" + n + "]";
}
}
}
if (m_destroy)
return 0;
json ff;
ff["name"] = p.name;
ff["sub_path"] = p.file;
ff["vendor"] = vendor;
ff["type"] = type;
ff["models"] = model_list;
ff["selected"] = 0;
m_ProfileJson["filament"][p.name] = ff;
}
wxGetApp().CallAfter([this] {
if (!m_destroy) {
//sync to appconfig first to populate current selections
SaveProfileData();
// Process list from visible system print presets
for (const Preset& p : bundle.prints()) {
if (!p.is_system || !p.vendor || !p.is_visible) continue;
json entry;
entry["name"] = p.name;
entry["sub_path"] = p.file;
m_ProfileJson["process"].push_back(entry);
}
//sync to web after selections are populated
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
if (require_all_resource_vendors) {
// If rsrc_vendor_dir has vendors (profile JSONs, or the preset caches a
// packaged build ships instead) not covered by the current bundle, the
// bundle is incomplete (e.g. dev env where data_dir/system only has
// OrcaFilamentLibrary+Custom). Fall back so the slow path reads both dirs.
try {
for (const std::string& name : vendor_names_in(rsrc_vendor_dir)) {
if (bundle.vendors.find(name) == bundle.vendors.end()) {
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << name
<< "' in resources but not in preset_bundle — falling back to JSON loading";
reset_profile_json();
return false;
}
}
} catch (const std::exception&) {}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
json m_Res = json::object();
m_Res["command"] = "userguide_profile_load_finish";
m_Res["sequence_id"] = "10001";
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data ("
<< m_ProfileJson["model"].size() << " models, "
<< m_ProfileJson["machine"].size() << " machines, "
<< m_ProfileJson["filament"].size() << " filaments)";
return !m_ProfileJson["machine"].empty();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileJson failed: " << e.what()
<< " — falling back to JSON loading";
reset_profile_json();
return false;
}
}
RunScript(strJS);
bool GuideFrame::BuildProfileDataFromPresetBundle()
{
PresetBundle* pb = wxGetApp().preset_bundle;
if (!pb || pb->vendors.empty())
return false;
return BuildProfileJson(*pb, /*require_all_resource_vendors=*/true);
}
bool GuideFrame::BuildProfileDataFromVendors()
{
try {
// Same vendor set and precedence as the JSON scan in LoadProfileData: a
// vendor in the user's system dir shadows the bundled one of that name.
// vendor_names_in names a vendor by its profile or, where a build ships
// preset caches instead, by its cache alone.
std::map<std::string, boost::filesystem::path> vendor_sources;
for (const boost::filesystem::path& dir : { vendor_dir, rsrc_vendor_dir }) {
boost::system::error_code ec;
if (boost::filesystem::exists(dir, ec))
for (const std::string& name : vendor_names_in(dir))
vendor_sources.emplace(name, dir); // first dir wins
}
// The load order: the filament library first, because the others'
// filaments inherit from it, then every versioned vendor — each loaded
// from the directory it was found in, so a vendor that is not installed
// is served from the shipped profiles. Each is stamped by name and
// version alone: a profile change requires a version bump, so those two
// determine content wherever the vendor's copy sits.
struct VendorSource { std::string name; boost::filesystem::path dir; std::string version; };
std::vector<VendorSource> ordered;
auto add_vendor = [&ordered](const std::string& name, const boost::filesystem::path& dir) {
// The version a load from `dir` would serve: the profile's where one
// exists (a cache is only served while it covers the profile beside
// it), the cache's own stamp where the cache is the whole vendor.
// A profile without a version (blacklist.json) carries no presets
// and is passed over.
const boost::filesystem::path profile = dir / (name + ".json");
if (boost::filesystem::exists(profile)) {
const Semver v = get_version_from_json(profile.string());
if (v.valid())
ordered.push_back({name, dir, v.to_string()});
} else {
ordered.push_back({name, dir,
VendorCacheFile::peek_version((dir / (name + ".opc")).string(), name)});
}
};
const std::string filament_library(PresetBundle::ORCA_FILAMENT_LIBRARY);
if (auto it = vendor_sources.find(filament_library); it != vendor_sources.end())
add_vendor(filament_library, it->second);
for (const auto& [name, dir] : vendor_sources)
if (name != filament_library)
add_vendor(name, dir);
if (ordered.empty())
return false;
json stamps = json::array();
for (const VendorSource& v : ordered)
stamps.push_back({v.name, v.version});
// What this function derives is a pure function of that stamped set, so
// the derived JSON is cached whole: a fresh cache makes an open one
// file read, with no bundle built and no preset installed. Stale or
// absent, the bundle is rebuilt below and the result written back.
const boost::filesystem::path cache_file =
boost::filesystem::path(Slic3r::data_dir()) / "cache" / "wizard_profile_data.json";
try {
// Slurped whole and parsed from the buffer — nlohmann's fastest
// input path; a stream adapter costs real time on a multi-MB file.
boost::nowide::ifstream ifs(cache_file.string(), std::ios::binary);
if (ifs.is_open()) {
const std::string text{std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>()};
json cached = json::parse(text);
if (cached.value("format", 0) == 1 && cached["vendors"] == stamps &&
! cached["profile"]["machine"].empty()) {
for (const char* key : { "model", "machine", "filament", "process" })
m_ProfileJson[key] = std::move(cached["profile"][key]);
BOOST_LOG_TRIVIAL(info) << "GuideFrame: profile data served from " << cache_file;
return true;
}
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(info) << "GuideFrame: rejecting cached profile data: " << e.what();
}
// Each vendor comes from its preset cache where one covers it, which is
// what makes this worth doing instead of the scan below; loading into a
// bundle per vendor keeps the install order the startup path has.
PresetBundle bundle;
auto load_vendor = [](PresetBundle& into, const std::string& vendor,
const boost::filesystem::path& dir, const PresetBundle* base) {
into.load_vendor_configs_from_json(dir.string(), vendor, PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent, base);
};
for (const VendorSource& v : ordered) {
if (*m_cancel_token)
return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
if (v.name == filament_library) {
load_vendor(bundle, v.name, v.dir, nullptr);
} else {
PresetBundle tmp;
load_vendor(tmp, v.name, v.dir, &bundle);
bundle.merge_presets(std::move(tmp));
}
}
if (bundle.vendors.empty())
return false;
if (! BuildProfileJson(bundle, /*require_all_resource_vendors=*/false))
return false;
// Written through a temp file and moved into place, as the preset caches
// are: half a cache must never be readable, and the PID suffix keeps two
// instances from interleaving on one temp file.
const std::string tmp_path = cache_file.string() + "." + std::to_string(get_current_pid()) + ".tmp";
try {
json out;
out["format"] = 1;
out["vendors"] = std::move(stamps);
json& profile = out["profile"];
for (const char* key : { "model", "machine", "filament", "process" })
profile[key] = m_ProfileJson[key];
boost::filesystem::create_directories(cache_file.parent_path());
{
boost::nowide::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc);
ofs << out.dump(-1, ' ', false, json::error_handler_t::ignore);
ofs.close();
if (! ofs.good())
throw std::runtime_error("write failed");
}
if (const std::error_code ec = rename_file(tmp_path, cache_file.string()))
throw std::runtime_error(ec.message());
} catch (const std::exception& e) {
boost::system::error_code rm;
boost::filesystem::remove(tmp_path, rm);
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: could not write the profile data cache: " << e.what();
}
return true;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: " << e.what();
reset_profile_json();
return false;
}
}
int GuideFrame::LoadProfileData()
{
// Background thread: the fast path in OnNavigationComplete failed (presets not yet loaded).
// Loading order (fastest to slowest):
// 1. Load every vendor, from its preset cache wherever one covers it
// 2. Read all vendor JSONs by hand
try {
if (!BuildProfileDataFromVendors()) {
// Last resort — read all vendor JSONs
std::set<std::string> loaded_vendors;
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
if (boost::filesystem::exists(vendor_dir / filament_library_name))
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
else
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
boost::filesystem::directory_iterator endIter;
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
if (!boost::filesystem::is_directory(*iter)) {
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
strVendor = strVendor.AfterLast('\\');
strVendor = strVendor.AfterLast('/');
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
continue;
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (*m_cancel_token) return 0;
}
boost::filesystem::directory_iterator others_endIter;
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
if (!boost::filesystem::is_directory(*iter)) {
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
strVendor = strVendor.AfterLast('\\');
strVendor = strVendor.AfterLast('/');
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
continue;
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (*m_cancel_token) return 0;
}
}
// Capture the cancel token by value (shared_ptr) so the lambda doesn't
// touch `this` if GuideFrame is destroyed before the event fires.
auto tok = m_cancel_token;
wxGetApp().CallAfter([this, tok] {
if (!*tok)
on_profile_loaded();
});
} catch (std::exception& e) {
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
// wxMessageBox(e.what(), "", MB_OK);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
}
filament_info_cache.clear();
+16 -2
View File
@@ -30,10 +30,14 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include <atomic>
#include <memory>
#include <unordered_map>
#include <nlohmann/json.hpp>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
class GuideFrame : public DPIDialog
@@ -78,6 +82,12 @@ public:
int LoadProfileData();
int SaveProfileData();
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
void init_guide_paths();
void on_profile_loaded();
bool BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors);
bool BuildProfileDataFromPresetBundle();
bool BuildProfileDataFromVendors();
void reset_profile_json();
int SaveProfile();
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
@@ -112,8 +122,11 @@ private:
//First Load
bool bFirstComplete{false};
bool m_destroy{false};
boost::thread* m_load_task{ nullptr };
// Set once in the destructor. Read through `this` by the loading thread
// (joined before `this` dies) and captured as the shared_ptr by CallAfter
// lambdas so they don't touch `this` after the object is freed.
std::shared_ptr<std::atomic<bool>> m_cancel_token{std::make_shared<std::atomic<bool>>(false)};
std::unique_ptr<boost::thread> m_load_task;
// User Config
bool PrivacyUse;
@@ -123,6 +136,7 @@ private:
bool InstallNetplugin;
bool network_plugin_ready {false};
json m_ProfileJson;
json m_OrcaFilaList;
std::string m_OrcaFilaLibPath;
+12 -20
View File
@@ -95,14 +95,11 @@ void Button::SetIcon(const wxString& icon)
}
}
void Button::SetInactiveIcon(const wxString &icon)
void Button::SetIcon(const wxBitmap& icon)
{
if (!icon.IsEmpty()) {
// BBS set button icon default size to 20
this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt());
} else {
this->inactive_icon = ScalableBitmap();
}
this->active_icon = ScalableBitmap();
this->active_icon.bmp() = icon;
messureSize();
Refresh();
}
@@ -257,12 +254,10 @@ void Button::SetStyle(const ButtonStyle style, const ButtonType type)
void Button::Rescale()
{
if (this->active_icon.bmp().IsOk())
// Only a named icon can be re-rasterized; one set from a wxBitmap has no source file,
if (!this->active_icon.name().empty())
this->active_icon.msw_rescale();
if (this->inactive_icon.bmp().IsOk())
this->inactive_icon.msw_rescale();
messureSize();
if(m_has_style)
@@ -293,11 +288,7 @@ void Button::render(wxDC& dc)
wxSize szIcon;
wxSize textSize = this->textSize.GetSize();
ScalableBitmap icon;
if (m_selected || ((states & (int)StateColor::State::Hovered) != 0))
icon = active_icon;
else
icon = inactive_icon;
const ScalableBitmap& icon = active_icon;
wxSize padding = this->paddingSize;
int spacing = 5;
// Wrap text
@@ -512,8 +503,8 @@ void Button::OnParentMotion(wxMouseEvent& event)
{
if (!tipWindow)
{
tipWindow = new wxTipWindow(this, tip);
tipWindow->Bind(wxEVT_DESTROY, [this](wxEvent& event) { this->tipWindow = nullptr;});
tipWindow = wxTipWindow::New(this, tip);
if (!tipWindow) return event.Skip();
tipWindow->Enable(false);
}
@@ -531,7 +522,8 @@ void Button::OnParentMotion(wxMouseEvent& event)
{
if (tipWindow)
{
delete tipWindow;
tipWindow->Dismiss();
tipWindow->Destroy();
tipWindow = nullptr;
}
}
@@ -552,7 +544,7 @@ void Button::OnParentLeave(wxMouseEvent& event)
if (!screen_rect.Contains(pos))
{
tipWindow->Dismiss();
delete tipWindow;
tipWindow->Destroy();
tipWindow = nullptr;
}
}
+3 -6
View File
@@ -3,6 +3,7 @@
#include "../wxExtensions.hpp"
#include "StaticBox.hpp"
#include <wx/tipwin.h>
class ButtonProps
{
@@ -27,14 +28,13 @@ enum class ButtonType{
Expanded , // Font14 Semi-Rounded For full length buttons. ex. buttons in static box
};
class wxTipWindow;
class Button : public StaticBox
{
wxTipWindow::Ref tipWindow;
wxRect textSize;
wxSize minSize; // set by outer
wxSize paddingSize;
ScalableBitmap active_icon;
ScalableBitmap inactive_icon;
StateColor text_color;
@@ -44,8 +44,6 @@ class Button : public StaticBox
bool isCenter = true;
bool vertical = false;
wxTipWindow* tipWindow = nullptr;
static const int buttonWidth = 200;
static const int buttonHeight = 50;
@@ -61,8 +59,7 @@ public:
bool SetFont(const wxFont& font) override;
void SetIcon(const wxString& icon);
void SetInactiveIcon(const wxString& icon);
void SetIcon(const wxBitmap& icon);
void SetMinSize(const wxSize& size) override;
void SetMaxSize(const wxSize& size) override;

Some files were not shown because too many files have changed in this diff Show More