Compare commits

..
13 changed files with 235 additions and 450 deletions
@@ -105,12 +105,11 @@ Rules:
`single_extruder_multi_material` off, and in the app when the printer tab loads a printer with a `single_extruder_multi_material` off, and in the app when the printer tab loads a printer with a
different number of extruders, the pair is rebuilt from `extruder_variant_list` (one different number of extruders, the pair is rebuilt from `extruder_variant_list` (one
`Direct Drive Standard` per extruder when the list is absent) and the variant arrays are resized to `Direct Drive Standard` per extruder when the list is absent) and the variant arrays are resized to
the rebuilt pair, padded with their first value or cut. The `machine_max_*` limits are padded the the rebuilt pair, padded with their first value or cut. The resize skips the `machine_max_*` limits
same way, so extruder 2 and up of a list-less printer take extruder 1's normal limit as their and `hotend_heating_rate` / `hotend_cooling_rate`: they keep their width, and an extruder beyond it
silent one too. The resize skips `hotend_heating_rate` / `hotend_cooling_rate`: they keep their reads their first value, so extruder 2 and up of a list-less printer take extruder 1's normal limit
width, and an extruder beyond it reads their first value. With the three in agreement that changes as their silent one too. With the three in agreement that changes nothing; a pair written without
nothing; a pair written without the list is replaced. A listed variant the pair lacks is a menu the list is replaced. A listed variant the pair lacks is a menu choice that reads variant index 0.
choice that reads variant index 0.
- The pair without `extruder_variant_list` slices, but the sidebar offers no variant switch and - The pair without `extruder_variant_list` slices, but the sidebar offers no variant switch and
the app cannot add variants to a list-less process: nothing is lost while every extruder the app cannot add variants to a list-less process: nothing is lost while every extruder
has exactly one variant, and every further variant is unreachable. has exactly one variant, and every further variant is unreachable.
+1 -7
View File
@@ -23,13 +23,7 @@ appimage_is_host_library() {
} }
appimage_is_elf_file() { appimage_is_elf_file() {
# Read the four-byte ELF magic rather than asking file(1): this runs once per candidate, and an file -b "$1" 2>/dev/null | grep -q '^ELF '
# AppDir holds thousands of them (the bundled Python runtime alone is ~5k files, none of them
# ELF). Two processes per call made this ~14 s of a ~95 s image build on its own.
local magic
[[ -f "$1" && -r "$1" ]] || return 1
IFS= read -r -n 4 -d '' magic < "$1" 2>/dev/null
[[ $magic == $'\177ELF' ]]
} }
appimage_list_direct_dependencies() { appimage_list_direct_dependencies() {
@@ -118,13 +118,9 @@ bundle_dependency_closure() {
# (scripts/check_appimage_libs.sh). # (scripts/check_appimage_libs.sh).
local -a search_dirs=("$dst_dir") local -a search_dirs=("$dst_dir")
# Walked with a read index rather than by reslicing the array: "${queue[@]:1}" rebuilds the whole while [ ${#queue[@]} -gt 0 ]; do
# queue on every pop, so draining it costs O(n^2). At the ~5k entries this bundle reaches that was target="${queue[0]}"
# ~22 s of the image build spent copying an array around. queue=("${queue[@]:1}")
local head=0
while [ "$head" -lt ${#queue[@]} ]; do
target="${queue[$head]}"
head=$((head + 1))
if [ ! -e "$target" ] || ! appimage_is_elf_file "$target"; then if [ ! -e "$target" ] || ! appimage_is_elf_file "$target"; then
continue continue
-7
View File
@@ -10030,13 +10030,6 @@ static void extend_extruder_variant(DynamicPrintConfig& config, const unsigned i
printer_extruder_variant_opt->values.insert(printer_extruder_variant_opt->values.end(), variants_list.begin(), variants_list.end()); printer_extruder_variant_opt->values.insert(printer_extruder_variant_opt->values.end(), variants_list.begin(), variants_list.end());
} }
} }
// 3. Size the machine limits to the rebuilt variants, padded with their first value like the other variant keys.
// They are not extruder option keys, so the resize loop in set_num_extruders skips them.
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : printer_options_with_variant_2)
if (auto *opt = config.option<ConfigOptionFloats>(key))
opt->resize(config.get_parameter_size(key, num_extruders), defaults.option(key));
} }
void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders)
+25 -58
View File
@@ -14,7 +14,6 @@
#include "Widgets/DialogButtons.hpp" #include "Widgets/DialogButtons.hpp"
#include "Widgets/StaticLine.hpp" #include "Widgets/StaticLine.hpp"
#include "Widgets/StateColor.hpp" #include "Widgets/StateColor.hpp"
#include "Widgets/SwitchButton.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Preset.hpp" #include "libslic3r/Preset.hpp"
@@ -865,25 +864,34 @@ void PublishSettingsDialog::build_option_model()
}; };
// --- Phase 1: printer per-extruder retraction settings (first, mirroring the sidebar's // --- Phase 1: printer per-extruder retraction settings (first, mirroring the sidebar's
// Printer group), from the printer tab's "Extruder" page. One inner tab per extruder (named // Printer group), from the printer tab's "Extruder"/"Extruder N" pages. One inner tab per
// as on the printer tab's switch, e.g. "Left Extruder"/"Right Extruder" via // extruder (e.g. "Left Extruder"/"Right Extruder" via Tab::translate_category), each holding
// Tab::translate_category), each holding that extruder's Retraction and Z-Hop rows with // that extruder's Retraction and Z-Hop rows with per-extruder "#N" values.
// per-extruder "#N" values.
{ {
size_t g = section_group_for(Section::Printer); size_t g = section_group_for(Section::Printer);
std::set<std::string> printer_added; std::set<std::string> printer_added;
for (Tab* tab : wxGetApp().tabs_list) { for (Tab* tab : wxGetApp().tabs_list) {
// The page's controls edit the extruder chosen on the printer tab's switch, so its if (tab->m_type != Preset::TYPE_PRINTER)
// option list is read once per extruder.
auto* printer_tab = dynamic_cast<TabPrinter*>(tab);
const Page* page = printer_tab ? printer_tab->extruder_page() : nullptr;
if (page == nullptr)
continue; continue;
const size_t extruders_count = printer_tab->m_extruders_count; for (const PageShp& page : tab->m_pages) {
for (size_t extruder_idx = 0; extruder_idx < extruders_count; ++extruder_idx) { if (!page->title().StartsWith("Extruder"))
const wxString page_title = Tab::translate_category(extruders_count > 1 ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString("Extruder"), tab->m_type); continue;
// Retraction and Z-Hop values are stored per variant column, not per extruder. // The extruder index of this page: its options are appended with the same
const int variant_index = printer_tab->extruder_variant_index(int(extruder_idx)); // "#N" opt_index (opt.second.second), so derive the tab's index from the first
// allowlisted option; skip the page when none is found (defensive).
int extruder_idx = -1;
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
if (optgroup->title != "Retraction" && optgroup->title != "Z-Hop")
continue;
for (const auto& opt : optgroup->opt_map())
if (extruder_idx < 0)
extruder_idx = opt.second.second;
if (extruder_idx >= 0)
break;
}
if (extruder_idx < 0)
continue;
const wxString page_title = Tab::translate_category(page->title(), tab->m_type);
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) { for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
// Allowlist on the untranslated optgroup title; the "Retraction when // Allowlist on the untranslated optgroup title; the "Retraction when
// switching material" group is intentionally skipped. // switching material" group is intentionally skipped.
@@ -891,17 +899,17 @@ void PublishSettingsDialog::build_option_model()
continue; continue;
const wxString subcategory = _(optgroup->title); const wxString subcategory = _(optgroup->title);
for (const auto& opt : optgroup->opt_map()) { for (const auto& opt : optgroup->opt_map()) {
const std::string& opt_id = opt.first;
const std::string& pure_key = opt.second.first; const std::string& pure_key = opt.second.first;
// Rows are keyed by the full per-extruder "#N" opt_id so each extruder // Rows are keyed by the full per-extruder "#N" opt_id so each extruder
// tab publishes its own value; GetPublishedKeys() emits the checked rows // tab publishes its own value; GetPublishedKeys() emits the checked rows
// as-is. // as-is.
const std::string opt_id = pure_key + "#" + std::to_string(variant_index);
if (!printer_added.insert(opt_id).second) if (!printer_added.insert(opt_id).second)
continue; continue;
wxString label, value, unit; wxString label, value, unit;
if (!option_text(opt_id, pure_key, label, value, unit)) if (!option_text(opt_id, pure_key, label, value, unit))
continue; continue;
size_t cat_index = category_index_for(page_title, Section::Printer, g, extruder_idx); size_t cat_index = category_index_for(page_title, Section::Printer, g, size_t(extruder_idx));
size_t sub_index = subcategory_index_for(cat_index, subcategory, optgroup->icon); size_t sub_index = subcategory_index_for(cat_index, subcategory, optgroup->icon);
add_row_ui(opt_id, label, value, unit, cat_index, sub_index); add_row_ui(opt_id, label, value, unit, cat_index, sub_index);
} }
@@ -1090,10 +1098,6 @@ void PublishSettingsDialog::build_option_model()
for (SectionGroup& section : m_sections) for (SectionGroup& section : m_sections)
if (!section.categories.empty()) if (!section.categories.empty())
section.tabs->SelectItem(0); section.tabs->SelectItem(0);
// Orca: the Printer section shows its extruders on the same switch as the printer tab's Extruder page.
for (size_t s = 0; s < m_sections.size(); ++s)
if (m_sections[s].kind == Section::Printer && m_sections[s].categories.size() > 1)
setup_variant_switch(s);
if (!m_sections.empty()) { if (!m_sections.empty()) {
m_outer_tabs->SelectItem(0); m_outer_tabs->SelectItem(0);
show_outer_page(0); show_outer_page(0);
@@ -1626,8 +1630,6 @@ void PublishSettingsDialog::show_inner_page(size_t section_index, int inner_inde
section.selected_mixed = -1; section.selected_mixed = -1;
} }
section.selected_inner = inner_index; section.selected_inner = inner_index;
if (section.variant_switch != nullptr)
section.variant_switch->SetSelection(inner_index); // fires its event, which ignores the shown page
Category& category = m_categories[section.categories[inner_index]]; Category& category = m_categories[section.categories[inner_index]];
category.page->Show(); category.page->Show();
category.scroll->FitInside(); category.scroll->FitInside();
@@ -1637,32 +1639,6 @@ void PublishSettingsDialog::show_inner_page(size_t section_index, int inner_inde
section.page_host_sizer->Layout(); section.page_host_sizer->Layout();
} }
void PublishSettingsDialog::setup_variant_switch(size_t section_index)
{
SectionGroup& section = m_sections[section_index];
std::vector<wxString> titles;
for (size_t category : section.categories)
titles.push_back(m_categories[category].title);
section.variant_switch = new MultiSwitchButton(section.page);
section.variant_switch->SetFitToOptions();
section.variant_switch->SetOptions(titles);
section.variant_switch->SetSelection(section.selected_inner);
section.variant_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this, section_index](wxCommandEvent& evt) {
evt.Skip();
// The hidden tab strip stays the selection model; its event shows the page.
SectionGroup& sec = m_sections[section_index];
if (evt.GetInt() != sec.selected_inner)
sec.tabs->SelectItem(evt.GetInt());
});
// The switch takes the place of the tab strip, centered like on the printer tab.
wxSizer* page_sizer = section.page->GetSizer();
page_sizer->Insert(1, section.variant_switch, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(4));
section.tabs->Hide();
section.page->Layout();
}
void PublishSettingsDialog::show_mixed_page(size_t section_index, int mixed_index) void PublishSettingsDialog::show_mixed_page(size_t section_index, int mixed_index)
{ {
if (section_index >= m_sections.size()) if (section_index >= m_sections.size())
@@ -2208,13 +2184,6 @@ void PublishSettingsDialog::refresh_tab_indicators()
for (size_t i = 0; i < section.categories.size(); ++i) { for (size_t i = 0; i < section.categories.size(); ++i) {
const bool on = category_has_selection(m_categories[section.categories[i]]); const bool on = category_has_selection(m_categories[section.categories[i]]);
section.tabs->SetItemIndicator(static_cast<unsigned int>(i), on); section.tabs->SetItemIndicator(static_cast<unsigned int>(i), on);
// The switch has no indicator dot; mark its option text instead.
if (section.variant_switch != nullptr) {
const wxString& title = m_categories[section.categories[i]].title;
const wxString text = on ? title + wxString(" ") + wxString(wxUniChar(0x2022)) : title;
if (section.variant_switch->GetOptionText(static_cast<unsigned int>(i)) != text)
section.variant_switch->SetOptionText(static_cast<unsigned int>(i), text);
}
any = any || on; any = any || on;
} }
if (section.mixed_tabs != nullptr) if (section.mixed_tabs != nullptr)
@@ -2279,8 +2248,6 @@ void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
section.tabs->Rescale(); section.tabs->Rescale();
if (section.mixed_tabs != nullptr) if (section.mixed_tabs != nullptr)
section.mixed_tabs->Rescale(); section.mixed_tabs->Rescale();
if (section.variant_switch != nullptr)
section.variant_switch->Rescale();
} }
// Refresh the per-row Color chips at the new DPI (they carry the slot number too). // Refresh the per-row Color chips at the new DPI (they carry the slot number too).
-7
View File
@@ -37,7 +37,6 @@ namespace Slic3r { class DynamicPrintConfig; }
class TextInput; class TextInput;
class StaticLine; class StaticLine;
class CheckBox; class CheckBox;
class MultiSwitchButton;
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@@ -196,10 +195,6 @@ private:
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
wxPanel* page{nullptr}; wxPanel* page{nullptr};
TabCtrl* tabs{nullptr}; TabCtrl* tabs{nullptr};
// Printer section with several extruders: the extruder switch shown instead of `tabs`, the
// same MultiSwitchButton (and option names) as the printer tab's Extruder page. One option
// per entry of `categories`, named by its title; `tabs` stays as the hidden selection model.
MultiSwitchButton* variant_switch{nullptr};
// Second tab strip, below the main one, listing only the mixed-color filament slots. // Second tab strip, below the main one, listing only the mixed-color filament slots.
// Present on the Material section only (null elsewhere). // Present on the Material section only (null elsewhere).
TabCtrl* mixed_tabs{nullptr}; TabCtrl* mixed_tabs{nullptr};
@@ -282,8 +277,6 @@ private:
bool row_is_visible(const Row& row) const; bool row_is_visible(const Row& row) const;
void apply_visibility(); void apply_visibility();
void bind_tab_events(); void bind_tab_events();
// Replaces a section's inner tab strip with a variant switch (see SectionGroup::variant_switch).
void setup_variant_switch(size_t section_index);
TabCtrl* m_outer_tabs{nullptr}; TabCtrl* m_outer_tabs{nullptr};
wxPanel* m_outer_host{nullptr}; wxPanel* m_outer_host{nullptr};
+104 -241
View File
@@ -811,12 +811,6 @@ wxString Tab::translate_category(const wxString& title, Preset::Type preset_type
} }
return _("Extruder") + title.SubString(8, title.Last()); return _("Extruder") + title.SubString(8, title.Last());
} }
// Orca: one "Extruder" page serves all extruders; name it "Extruders" when there are several.
if (preset_type == Preset::TYPE_PRINTER && title == "Extruder") {
auto preset = wxGetApp().preset_bundle;
if (preset && preset->get_printer_extruder_count() > 1)
return _("Extruders");
}
return _(title); return _(title);
} }
@@ -1159,38 +1153,6 @@ std::string Tab::options_list_storage_key(const std::string& opt_key) const
return (serialized || is_plugin_field) ? opt_key : opt_key + "#0"; return (serialized || is_plugin_field) ? opt_key : opt_key + "#0";
} }
// Orca: deep_diff() flags every vector entry at or past the reference vector's length as changed,
// whatever its value (e.g. the values of an extruder added by raising the extruder count). A vector
// grows by copying its first entry (ConfigOptionVector::resize), so such an entry only counts as
// changed when it differs from the reference's first entry, as before Orca's deep_diff() change.
// The change of the count itself shows on "extruders_count".
static void drop_unchanged_added_entries(std::vector<std::string> &options, const DynamicPrintConfig &current, const Preset *reference)
{
if (reference == nullptr)
return;
// deep_diff() lists a key's entries one after another, so serialize each key's vectors once.
std::string serialized_key;
std::vector<std::string> cur_values;
std::string ref_first;
options.erase(std::remove_if(options.begin(), options.end(), [&](const std::string &opt) {
const auto pos = opt.find('#');
if (pos == std::string::npos)
return false;
const std::string key = opt.substr(0, pos);
const size_t idx = size_t(std::atoi(opt.c_str() + pos + 1));
auto ref = dynamic_cast<const ConfigOptionVectorBase *>(reference->config.option(key));
auto cur = dynamic_cast<const ConfigOptionVectorBase *>(current.option(key));
if (ref == nullptr || cur == nullptr || idx < ref->size() || ref->size() == 0 || idx >= cur->size())
return false;
if (key != serialized_key) {
serialized_key = key;
cur_values = cur->vserialize();
ref_first = ref->vserialize().front();
}
return cur_values[idx] == ref_first;
}), options.end());
}
void Tab::update_all_extruder_options_status() void Tab::update_all_extruder_options_status()
{ {
if (!m_extruder_switch && !m_variant_combo) { if (!m_extruder_switch && !m_variant_combo) {
@@ -1221,8 +1183,6 @@ void Tab::update_all_extruder_options_status()
auto dirty_options = m_presets->current_dirty_options(true); auto dirty_options = m_presets->current_dirty_options(true);
auto nonsys_options = m_presets->current_different_from_parent_options(true); auto nonsys_options = m_presets->current_different_from_parent_options(true);
if (m_type == Preset::TYPE_PRINTER)
update_custom_dirty(dirty_options, nonsys_options);
auto filter_extruder_options = [](const std::vector<std::string>& options) { auto filter_extruder_options = [](const std::vector<std::string>& options) {
std::vector<std::string> filtered_options; std::vector<std::string> filtered_options;
for (const auto& opt : options) { for (const auto& opt : options) {
@@ -1271,7 +1231,7 @@ void Tab::update_extruder_switch_colors()
if (m_active_page) { if (m_active_page) {
if (m_active_page->title() == "Speed" || m_active_page->title() == "Motion ability" || m_active_page->title() == "Filament" || if (m_active_page->title() == "Speed" || m_active_page->title() == "Motion ability" || m_active_page->title() == "Filament" ||
m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multimaterial" || is_printer_extruder_page(m_active_page)) { m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multimaterial") {
for (auto page_ptr : m_pages) { for (auto page_ptr : m_pages) {
if (page_ptr.get() == m_active_page) { if (page_ptr.get() == m_active_page) {
pages_to_check.push_back(page_ptr); pages_to_check.push_back(page_ptr);
@@ -1302,8 +1262,8 @@ void Tab::update_extruder_switch_colors()
void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check) void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check)
{ {
int config_index = index; int config_index = index;
int extruder_id = index;
if (m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_MODEL) { if (m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_MODEL) {
int extruder_id;
NozzleVolumeType nozzle_type; NozzleVolumeType nozzle_type;
parse_extruder_selection(index, extruder_id, nozzle_type); parse_extruder_selection(index, extruder_id, nozzle_type);
@@ -1336,10 +1296,6 @@ void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &mod
} }
std::string target_opt_key = base_opt_key + "#" + std::to_string(config_index * stride); std::string target_opt_key = base_opt_key + "#" + std::to_string(config_index * stride);
// Orca: on the printer's Extruder page only per-variant options use the variant column,
// the others (nozzle_diameter, extruder_offset, ...) are indexed by the extruder.
if (is_printer_extruder_page(page.get()) && printer_options_with_variant_1.count(base_opt_key) == 0)
target_opt_key = base_opt_key + "#" + std::to_string(extruder_id);
auto status_iter = m_all_extruder_options_status.find(target_opt_key); auto status_iter = m_all_extruder_options_status.find(target_opt_key);
if (status_iter != m_all_extruder_options_status.end()) { if (status_iter != m_all_extruder_options_status.end()) {
@@ -1380,6 +1336,18 @@ void TabPrinter::init_options_list()
Tab::init_options_list(); Tab::init_options_list();
if (m_printer_technology == ptFFF) if (m_printer_technology == ptFFF)
m_options_list.emplace("extruders_count", m_opt_status_value); m_options_list.emplace("extruders_count", m_opt_status_value);
for (size_t i = 1; i < m_extruders_count; ++i) {
wxString target_title = wxString::Format("Extruder %d", int(i + 1));
for (auto &page : m_pages) {
if (page->title() == target_title) {
for (auto group : page->m_optgroups) {
for (auto &opt : group->opt_map())
m_options_list.emplace(opt.first, m_opt_status_value);
}
break;
}
}
}
} }
void TabPrinter::msw_rescale() void TabPrinter::msw_rescale()
@@ -1469,7 +1437,7 @@ void Tab::update_changed_tree_ui()
get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); get_sys_and_mod_flags("compatible_printers", sys_page, modified_page);
} }
} }
if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multimaterial" || is_printer_extruder_page(page.get())) { if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multimaterial") {
auto options = generate_extruder_options(); auto options = generate_extruder_options();
for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) { for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) {
std::vector<PageShp> pages_to_check = { page }; std::vector<PageShp> pages_to_check = { page };
@@ -1572,10 +1540,6 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
m_postpone_update_ui = false; m_postpone_update_ui = false;
// Orca: the restored config may have another extruder count than the tab shows.
if (auto printer_tab = dynamic_cast<TabPrinter *>(this))
printer_tab->sync_extruders_count();
// When all values are rolled, then we have to update whole tab in respect to the reverted values // When all values are rolled, then we have to update whole tab in respect to the reverted values
update(); update();
if (m_active_page) if (m_active_page)
@@ -1913,17 +1877,6 @@ static wxString pad_combo_value_for_config(const DynamicPrintConfig &config)
return config.opt_bool("pad_enable") ? (config.opt_bool("pad_around_object") ? _("Around object") : _("Below object")) : _("None"); return config.opt_bool("pad_enable") ? (config.opt_bool("pad_around_object") ? _("Around object") : _("Below object")) : _("None");
} }
// Rebuilds the variant switch of every tab that has one, e.g. after the extruder count or a nozzle volume type changed.
static void update_all_extruder_variants(int extruder_idx = -1)
{
for (auto tab : wxGetApp().tabs_list)
tab->update_extruder_variants(extruder_idx);
if (auto tab = wxGetApp().plate_tab)
tab->update_extruder_variants(extruder_idx);
for (auto tab : wxGetApp().model_tabs_list)
tab->update_extruder_variants(extruder_idx);
}
void Tab::on_value_change(const std::string& opt_key, const boost::any& value) void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
{ {
// Orca: // Orca:
@@ -2366,7 +2319,15 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key.find("nozzle_volume_type") != std::string::npos) { if (opt_key.find("nozzle_volume_type") != std::string::npos) {
int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str()); int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str());
update_all_extruder_variants(extruder_idx); for (auto tab : wxGetApp().tabs_list) {
tab->update_extruder_variants(extruder_idx);
}
if (auto tab = wxGetApp().plate_tab) {
tab->update_extruder_variants(extruder_idx);
}
for (auto tab : wxGetApp().model_tabs_list) {
tab->update_extruder_variants(extruder_idx);
}
if (wxGetApp().app_config->get("auto_calculate_flush") == "all") { if (wxGetApp().app_config->get("auto_calculate_flush") == "all") {
wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1,extruder_idx); wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1,extruder_idx);
} }
@@ -5724,13 +5685,9 @@ if (is_marlin_flavor)
size_t extruders_count = size_t(boost::any_cast<int>(v)); size_t extruders_count = size_t(boost::any_cast<int>(v));
wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { wxTheApp->CallAfter([this, opt_key, value, extruders_count]() {
if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") {
const size_t old_extruders_count = m_extruders_count;
extruders_count_changed(extruders_count); extruders_count_changed(extruders_count);
init_options_list(); // m_options_list should be updated before UI updating init_options_list(); // m_options_list should be updated before UI updating
update_dirty(); update_dirty();
// Orca: the variant switches (here, Process in the sidebar, ...) list one option per extruder.
if (m_extruders_count != old_extruders_count)
update_all_extruder_variants();
if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages
on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped
@@ -5740,7 +5697,6 @@ if (is_marlin_flavor)
// Orca: we use a different logic here. If SEMM is enabled, we set extruder count to 1. // Orca: we use a different logic here. If SEMM is enabled, we set extruder count to 1.
#if 1 #if 1
extruders_count_changed(1); extruders_count_changed(1);
update_all_extruder_variants();
#else #else
std::vector<double> nozzle_diameters = std::vector<double> nozzle_diameters =
@@ -5812,13 +5768,14 @@ if (is_marlin_flavor)
m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page);
} }
// Orca: a single "Extruder" page serves all extruders. Its controls are created once for extruder 0; // Orca: build missed extruder pages
// switch_excluder() re-targets them to the extruder selected on the variant switch (m_extruder_switch), for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) {
// which the printer tab shows on this page too. const wxString& page_name = (m_extruders_count > 1) ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString::Format("Extruder");
if (extruder_page() == nullptr && m_extruders_count > 0) {
const size_t extruder_idx = 0; //# build page
auto page = add_options_page(L("Extruder"), "custom-gcode_extruder", true); // ORCA: icon only visible on placeholders //const wxString& page_name = wxString::Format("Extruder %d", int(extruder_idx + 1));
m_pages.insert(m_pages.begin() + n_before_extruders, page); auto page = add_options_page(page_name, "custom-gcode_extruder", true); // ORCA: icon only visible on placeholders
m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page);
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true); auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx); optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
@@ -5831,10 +5788,8 @@ if (is_marlin_flavor)
option.opt.full_width = true; option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_extruder_basic_information#extruder-offset-position"); optgroup->append_single_option_line(option, "printer_extruder_basic_information#extruder-offset-position");
optgroup->m_on_change = [this](const t_config_option_key& opt_key, boost::any value) optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
{ {
// The page edits the extruder selected on the variant switch.
const size_t extruder_idx = size_t(get_current_active_extruder());
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material"); bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter")) if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter"))
{ {
@@ -5929,22 +5884,22 @@ if (is_marlin_flavor)
//optgroup->append_line(line); //optgroup->append_line(line);
#endif #endif
} }
// BBS. No extra extruder page for single physical extruder machine
// The page holds only the "#0" controls, so register every extruder's options with the search // # remove extra pages
// index under "Extruder N"; a search hit selects that extruder (TabPrinter::activate_option()). auto &first_extruder_title = const_cast<wxString &>(m_pages[n_before_extruders]->title());
if (Page *page = extruder_page()) { if (m_extruders_count < m_extruders_count_old) {
auto &index = wxGetApp().sidebar().settings_index(); m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count,
for (auto &group : page->m_optgroups) { m_pages.begin() + n_before_extruders + m_extruders_count_old);
group->set_config_category_and_type(page->title(), m_type); if (m_extruders_count == 1)
for (auto &opt : group->opt_map()) { first_extruder_title = wxString::Format("Extruder");
if (opt.second.second < 0) } else if (m_extruders_count_old == 1) {
continue; first_extruder_title = wxString::Format("Extruder %d", 1);
for (size_t i = 0; i < m_extruders_count; ++i) { }
const wxString category = m_extruders_count > 1 ? wxString::Format("Extruder %d", int(i + 1)) : wxString("Extruder"); auto & index = wxGetApp().sidebar().settings_index();
index.add_key(opt.second.first + "#" + std::to_string(i), m_type, group->title, category, group->icon); for (auto &group : m_pages[n_before_extruders]->m_optgroups) {
} group->set_config_category_and_type(first_extruder_title, m_type);
} for (auto &opt : group->opt_map())
} index.add_key(opt.first + "#0", m_type, group->title, first_extruder_title, group->icon);
} }
Thaw(); Thaw();
@@ -6225,6 +6180,12 @@ void TabPrinter::toggle_options()
return; return;
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type"); auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = m_config->option<ConfigOptionEnumsGeneric>("extruder_type");
auto get_index_for_extruder =
[this, &extruders](int extruder_id, int stride = 1) {
return m_config->get_index_for_extruder(extruder_id + 1, "printer_extruder_id",
ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), "printer_extruder_variant", stride);
};
//BBS: whether the preset is Bambu Lab printer //BBS: whether the preset is Bambu Lab printer
bool is_BBL_printer = false; bool is_BBL_printer = false;
@@ -6299,13 +6260,14 @@ void TabPrinter::toggle_options()
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1); 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); toggle_option("wait_for_temp_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
} }
if (m_active_page->title() == L("Extruder") && m_extruders_count > 0) wxString extruder_number;
long val = 1;
if ( m_active_page->title().IsSameAs(L("Extruder")) ||
(m_active_page->title().StartsWith("Extruder ", &extruder_number) && extruder_number.ToLong(&val) &&
val > 0 && (size_t)val <= m_extruders_count))
{ {
// Orca: the single Extruder page edits the extruder selected on the variant switch; its size_t i = size_t(val - 1);
// controls carry field index 0 (i), the values are read for `extruder`. int variant_index = get_index_for_extruder(i);
const size_t i = 0;
const size_t extruder = std::min<size_t>(size_t(get_current_active_extruder()), m_extruders_count - 1);
const int variant_index = extruder_variant_index(int(extruder));
bool have_retract_length = m_config->opt_float("retraction_length", variant_index) > 0; bool have_retract_length = m_config->opt_float("retraction_length", variant_index) > 0;
toggle_option("extruder_printable_area", false, i); // disable toggle_option("extruder_printable_area", false, i); // disable
@@ -6332,7 +6294,7 @@ void TabPrinter::toggle_options()
vec.resize(0); vec.resize(0);
vec = {"retract_lift_above", "retract_lift_below", "retract_lift_enforce"}; vec = {"retract_lift_above", "retract_lift_below", "retract_lift_enforce"};
for (auto el : vec) for (auto el : vec)
toggle_option(el, retraction && (m_config->opt_float("z_hop", variant_index) > 0), i); toggle_option(el, retraction && (m_config->opt_float("z_hop", i) > 0), i);
// some options only apply when not using firmware retraction // some options only apply when not using firmware retraction
vec.resize(0); vec.resize(0);
@@ -6385,7 +6347,7 @@ void TabPrinter::toggle_options()
toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_int("enable_long_retraction_when_cut"), i); toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_int("enable_long_retraction_when_cut"), i);
toggle_line("retraction_distances_when_cut", m_config->opt_bool("long_retractions_when_cut", variant_index), i); toggle_line("retraction_distances_when_cut", m_config->opt_bool("long_retractions_when_cut", variant_index), i);
toggle_option("travel_slope", m_config->opt_enum("z_hop_types", variant_index) != ZHopType::zhtNormal, i); toggle_option("travel_slope", m_config->opt_enum("z_hop_types", i) != ZHopType::zhtNormal, i);
} }
if (m_active_page->title() == L("Motion ability")) { if (m_active_page->title() == L("Motion ability")) {
@@ -6459,16 +6421,12 @@ void TabPrinter::on_value_change(const std::string& opt_key, const boost::any& v
return; return;
const int pos = opt_key.find("#"); const int pos = opt_key.find("#");
// Orca: fields of the single Extruder page keep index 0 but edit the selected extruder.
const int data_idx = pos > 0 ? extruder_page_data_index(opt_key) : -1;
if (pos > 0) { if (pos > 0) {
std::string temp_str = opt_key; std::string temp_str = opt_key;
boost::erase_head(temp_str, pos + 1); boost::erase_head(temp_str, pos + 1);
int orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str())); int orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
int opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0; int opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
if (data_idx >= 0)
opt_idx = data_idx;
std::string opt_key_pure = opt_key; std::string opt_key_pure = opt_key;
boost::erase_tail(opt_key_pure, opt_key_pure.size() - pos); boost::erase_tail(opt_key_pure, opt_key_pure.size() - pos);
@@ -6503,12 +6461,7 @@ void TabPrinter::on_value_change(const std::string& opt_key, const boost::any& v
} }
} }
// Orca: report Extruder page changes as "key#<extruder>", as the former "Extruder N" pages did, Tab::on_value_change(opt_key, value);
// so per-extruder handling in Tab::on_value_change() sees the extruder that was edited.
if (data_idx >= 0)
Tab::on_value_change(opt_key.substr(0, pos) + "#" + std::to_string(get_current_active_extruder()), value);
else
Tab::on_value_change(opt_key, value);
} }
void TabPrinter::update() void TabPrinter::update()
@@ -7440,7 +7393,12 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
// update_undo_buttons(); // update_undo_buttons();
this->OnActivate(); this->OnActivate();
m_parent->set_active_tab(this); m_parent->set_active_tab(this);
update_variant_sizer_visibility(); if (m_variant_sizer) {
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder "));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
m_page_view->Thaw(); m_page_view->Thaw();
return false; return false;
@@ -7451,7 +7409,12 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
return false; return false;
m_active_page = page; m_active_page = page;
update_variant_sizer_visibility(); if (m_variant_sizer) {
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder"));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
auto throw_if_canceled = std::function<void()>([this](){ auto throw_if_canceled = std::function<void()>([this](){
#ifdef WIN32 #ifdef WIN32
@@ -8277,11 +8240,9 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
m_actual_nozzle_volumes.resize(extruder_nums, NozzleVolumeType::nvtStandard); m_actual_nozzle_volumes.resize(extruder_nums, NozzleVolumeType::nvtStandard);
for (int i = 0; i < extruder_nums; i++) m_actual_nozzle_volumes[i] = (NozzleVolumeType)nozzle_volumes->values[i]; for (int i = 0; i < extruder_nums; i++) m_actual_nozzle_volumes[i] = (NozzleVolumeType)nozzle_volumes->values[i];
// Orca: when every extruder uses the same variant (e.g. a non-Bambu dual-nozzle printer), the // Orca: a non-Bambu dual-nozzle printer has two extruders but a single variant column, so
// switch has no nozzle variants to select and nothing to sync. The printer tab still enables it // the nozzle switch and sync button have nothing to act on. Only enable with real variants.
// to choose the extruder its Extruder and Motion ability pages edit. if (extruder_nums >= 2 && m_preset_bundle->support_different_extruders()) {
m_extruder_switch_variants = extruder_nums >= 2 && m_preset_bundle->support_different_extruders();
if (m_extruder_switch_variants || (m_type == Preset::TYPE_PRINTER && extruder_nums >= 2)) {
auto options = generate_extruder_options(); auto options = generate_extruder_options();
m_extruder_switch->SetOptions(options); m_extruder_switch->SetOptions(options);
@@ -8320,8 +8281,12 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
if (m_type == Preset::TYPE_PRINT) { if (m_type == Preset::TYPE_PRINT) {
update_pages_with_multi_variant(); update_pages_with_multi_variant();
} }
update_extruder_switch_colors(); if (m_variant_sizer) {
update_variant_sizer_visibility(); wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder "));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
} }
// The variant switch tags are the narrowest place a volume type is named, so they abbreviate it; // The variant switch tags are the narrowest place a volume type is named, so they abbreviate it;
@@ -8396,21 +8361,12 @@ std::vector<wxString> Tab::generate_extruder_options()
return options; return options;
} }
// Orca: the printer tab has one tag per extruder, named as its former "Extruder N" pages were.
// parse_extruder_selection() counts a hybrid extruder as two tags; that still lines up because
// only the last extruder can be hybrid.
if (m_type == Preset::TYPE_PRINTER) {
for (int i = 0; i < extruder_nums; ++i)
options.push_back(translate_category(wxString::Format("Extruder %d", i + 1), m_type));
return options;
}
std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle); std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle);
// Orca: the main/deputy toolhead names describe a Bambu dual-nozzle printer, where extruder 0 is // Orca: the main/deputy toolhead names describe a dual-nozzle printer, where extruder 0 is the
// the left (deputy) and extruder 1 the right (main) nozzle. Other printers number their tools. // left (deputy) and extruder 1 the right (main) nozzle. From three extruders on the tools are
const bool toolhead_names = extruder_nums == 2 && m_preset_bundle->is_bbl_vendor(); // interchangeable, so name them by index instead of repeating one side.
for (int i = 0; i < extruder_nums; ++i) { for (int i = 0; i < extruder_nums; ++i) {
wxString extruder_name = !toolhead_names ? wxString::Format("T%d", i + 1) : wxString extruder_name = extruder_nums > 2 ? wxString::Format("T%d", i + 1) :
_L(DevPrinterConfigUtil::get_toolhead_display_name( _L(DevPrinterConfigUtil::get_toolhead_display_name(
pt, (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID, pt, (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID,
ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true)); ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
@@ -8427,71 +8383,6 @@ std::vector<wxString> Tab::generate_extruder_options()
return options; return options;
} }
Page *TabPrinter::extruder_page() const
{
for (const PageShp &page : m_pages)
if (page->title() == "Extruder")
return page.get();
return nullptr;
}
int TabPrinter::extruder_page_data_index(const std::string &field_id) const
{
if (Page *page = extruder_page())
for (const auto &group : page->m_optgroups)
if (auto it = group->opt_map().find(field_id); it != group->opt_map().end())
return it->second.second;
return -1;
}
int TabPrinter::extruder_variant_index(int extruder)
{
const auto *extruders = m_config->option<ConfigOptionEnumsGeneric>("extruder_type");
const int index = extruder < int(extruders->size()) ?
m_config->get_index_for_extruder(extruder + 1, "printer_extruder_id", ExtruderType(extruders->values[extruder]),
get_actual_nozzle_volume_type(extruder), "printer_extruder_variant") :
-1;
return index < 0 ? extruder : index;
}
void TabPrinter::update_custom_dirty(std::vector<std::string> &dirty_options, std::vector<std::string> &nonsys_options)
{
drop_unchanged_added_entries(dirty_options, *m_config, &m_presets->get_selected_preset());
drop_unchanged_added_entries(nonsys_options, *m_config, m_presets->get_selected_preset_parent());
}
void TabPrinter::sync_extruders_count()
{
if (m_printer_technology != ptFFF)
return;
const auto *nozzle_diameter = m_config->option<ConfigOptionFloats>("nozzle_diameter");
if (nozzle_diameter == nullptr || nozzle_diameter->size() == m_extruders_count)
return;
extruders_count_changed(nozzle_diameter->size());
init_options_list();
update_all_extruder_variants();
}
void TabPrinter::activate_option(const std::string &opt_key, const wxString &category)
{
wxString number;
long n = 0;
const bool numbered = category.StartsWith("Extruder ", &number) && number.ToLong(&n);
if (extruder_page() == nullptr || (!numbered && category != "Extruder")) {
Tab::activate_option(opt_key, category);
return;
}
// Selecting fires the switch's event, which re-targets the page (switch_excluder()).
const int extruder_idx = (n >= 1 && n <= long(m_extruders_count)) ? int(n - 1) : 0;
if (m_extruder_switch && m_extruder_switch->IsThisEnabled() && extruder_idx != get_current_active_extruder())
m_extruder_switch->SetSelection(calculate_selection_index_for_extruder(extruder_idx, get_actual_nozzle_volume_type(extruder_idx)));
// The page's controls are created for index 0.
const auto pos = opt_key.find('#');
Tab::activate_option(pos == std::string::npos ? opt_key : opt_key.substr(0, pos) + "#0", "Extruder");
}
NozzleVolumeType Tab::get_actual_nozzle_volume_type(int extruder_id) NozzleVolumeType Tab::get_actual_nozzle_volume_type(int extruder_id)
{ {
int extruder_count = m_preset_bundle->get_printer_extruder_count(); int extruder_count = m_preset_bundle->get_printer_extruder_count();
@@ -8568,44 +8459,18 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
return false; return false;
} }
bool Tab::variant_switch_active() const
{
if (m_extruder_switch)
return m_extruder_switch->IsThisEnabled();
return m_variant_combo && m_variant_combo->IsThisEnabled();
}
void Tab::update_variant_sizer_visibility()
{
if (!m_variant_sizer)
return;
const bool show = variant_switch_active() && m_active_page && !m_active_page->m_opt_id_map.empty();
m_main_sizer->Show(m_variant_sizer, show);
if (m_extruder_sync) {
m_extruder_sync->Show(show);
// Orca: copying between extruders is offered only between nozzle variants, and not on the Extruder page.
m_extruder_sync->Enable(m_extruder_switch_variants && !is_printer_extruder_page(m_active_page) &&
get_extruder_sync_enable_state(get_current_active_extruder()));
}
GetParent()->Layout();
}
void Tab::switch_excluder(int extruder_id, bool reload) void Tab::switch_excluder(int extruder_id, bool reload)
{ {
Preset & printer_preset = m_preset_bundle->printers.get_edited_preset(); Preset & printer_preset = m_preset_bundle->printers.get_edited_preset();
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type"); auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type"); auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type");
// Orca: the printer tab re-targets its pages to the extruder selected on the switch. Resolved before
// the range check below, which keeps the extruder_type read in get_index_for_extruder in range.
if (m_type == Preset::TYPE_PRINTER && extruder_id == -1)
extruder_id = get_current_active_extruder();
if (!m_variant_combo && (extruder_id >= (int)nozzle_volumes->size() || extruder_id >= (int)extruders->size())) if (!m_variant_combo && (extruder_id >= (int)nozzle_volumes->size() || extruder_id >= (int)extruders->size()))
extruder_id = 0; extruder_id = 0;
if (m_extruder_switch) { if (m_extruder_switch) {
int current_extruder = get_current_active_extruder(); int current_extruder = get_current_active_extruder();
bool sync_enable = get_extruder_sync_enable_state(current_extruder); bool sync_enable = get_extruder_sync_enable_state(current_extruder);
m_extruder_sync->Enable(m_extruder_switch_variants && sync_enable && !is_printer_extruder_page(m_active_page)); m_extruder_sync->Enable(m_extruder_switch->IsThisEnabled() && sync_enable);
m_extruder_sync->Show(); m_extruder_sync->Show();
if (m_type != Preset::TYPE_PRINTER) { if (m_type != Preset::TYPE_PRINTER) {
if (extruder_id == -1) if (extruder_id == -1)
@@ -8632,26 +8497,24 @@ void Tab::switch_excluder(int extruder_id, bool reload)
return; return;
if (m_extruder_switch) m_extruder_switch->SetClientData(reinterpret_cast<void*>(static_cast<std::uintptr_t>(index))); if (m_extruder_switch) m_extruder_switch->SetClientData(reinterpret_cast<void*>(static_cast<std::uintptr_t>(index)));
if (m_variant_combo) m_variant_combo->SetClientData(reinterpret_cast<void *>(static_cast<std::uintptr_t>(index))); if (m_variant_combo) m_variant_combo->SetClientData(reinterpret_cast<void *>(static_cast<std::uintptr_t>(index)));
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
for (auto page : m_pages) { for (auto page : m_pages) {
bool is_extruder = false; bool is_extruder = false;
int page_index = index;
if (m_type == Preset::TYPE_PRINTER) { if (m_type == Preset::TYPE_PRINTER) {
if (page->title() == "Extruder") if (page->title().StartsWith("Extruder")) {
int extruder_id2 = std::atoi(page->title().Mid(9).ToUTF8()) - 1;
if (extruder_id >= 0 && extruder_id2 != extruder_id)
continue;
if (extruder_id2 > 0)
index = get_index_for_extruder(extruder_id2);
is_extruder = true; is_extruder = true;
else if (page->title().StartsWith("Motion ability")) } else if (page->title().StartsWith("Motion ability")) {
page_index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2); index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2);
}
} }
page->m_opt_id_map.clear(); page->m_opt_id_map.clear();
for (auto group : page->m_optgroups) { for (auto group : page->m_optgroups) {
for (auto &opt : group->opt_map()) { for (auto &opt : group->opt_map()) {
if (is_extruder && opt.second.second >= 0) {
// Per-variant options use the variant column, the others (nozzle_diameter,
// extruder_offset, ...) are sized by the extruder count and use the extruder.
const int idx = printer_options_with_variant_1.count(opt.second.first) > 0 ? page_index : extruder_id;
const_cast<int &>(opt.second.second) = idx;
page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(idx), opt.first});
continue;
}
auto iter = std::find(printer_extruder_options.begin(), printer_extruder_options.end(), opt.second.first); auto iter = std::find(printer_extruder_options.begin(), printer_extruder_options.end(), opt.second.first);
if (iter != printer_extruder_options.end()) { if (iter != printer_extruder_options.end()) {
page->m_opt_id_map.insert({opt.first, opt.first}); page->m_opt_id_map.insert({opt.first, opt.first});
@@ -8659,9 +8522,9 @@ void Tab::switch_excluder(int extruder_id, bool reload)
} }
if (opt.second.second >= 0) { if (opt.second.second >= 0) {
const_cast<int &>(opt.second.second) = page_index; const_cast<int &>(opt.second.second) = index;
page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(page_index), opt.first}); page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(index), opt.first});
group->draw_multi_extruder = variant_switch_active(); group->draw_multi_extruder = !is_extruder && variant_ctrl->IsThisEnabled();
} }
} }
} }
+1 -31
View File
@@ -331,10 +331,6 @@ public:
MultiSwitchButton * m_variant_combo = nullptr; MultiSwitchButton * m_variant_combo = nullptr;
ScalableButton *m_extruder_sync = nullptr; ScalableButton *m_extruder_sync = nullptr;
wxPanel * m_extruder_sync_box = nullptr; wxPanel * m_extruder_sync_box = nullptr;
// Orca: whether m_extruder_switch switches nozzle variants (it then offers sync between them).
// The printer tab also enables the switch for printers without variants, to choose the extruder
// its Extruder and Motion ability pages edit.
bool m_extruder_switch_variants = false;
std::vector<NozzleVolumeType> m_actual_nozzle_volumes; std::vector<NozzleVolumeType> m_actual_nozzle_volumes;
public: public:
@@ -391,13 +387,6 @@ public:
void update_changed_tree_ui(); void update_changed_tree_ui();
void update_undo_buttons(); void update_undo_buttons();
void update_extruder_switch_colors(); void update_extruder_switch_colors();
// Whether the variant switch (m_extruder_switch / m_variant_combo) is enabled: on the printer tab for
// any multi-extruder printer, on the other tabs when it switches nozzle variants.
bool variant_switch_active() const;
// Orca: whether `page` is the printer tab's single "Extruder" page, which edits the extruder selected on the switch.
bool is_printer_extruder_page(const Page* page) const { return m_type == Preset::TYPE_PRINTER && page && page->title() == "Extruder"; }
// Shows the variant switch row on pages with options that follow it.
void update_variant_sizer_visibility();
void update_all_extruder_options_status(); void update_all_extruder_options_status();
void check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check); void check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check);
@@ -455,7 +444,7 @@ public:
virtual void on_value_change(const std::string& opt_key, const boost::any& value); virtual void on_value_change(const std::string& opt_key, const boost::any& value);
void update_wiping_button_visibility(); void update_wiping_button_visibility();
virtual void activate_option(const std::string& opt_key, const wxString& category); void activate_option(const std::string& opt_key, const wxString& category);
void apply_searcher(); void apply_searcher();
void cache_config_diff(const std::vector<std::string>& selected_options, const DynamicPrintConfig* config = nullptr); void cache_config_diff(const std::vector<std::string>& selected_options, const DynamicPrintConfig* config = nullptr);
void apply_config_from_cache(); void apply_config_from_cache();
@@ -721,25 +710,6 @@ public:
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr); void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
bool apply_extruder_cnt_from_cache(); bool apply_extruder_cnt_from_cache();
void refresh_printer_agent_dropdown() const; void refresh_printer_agent_dropdown() const;
// Orca: a single "Extruder" page for all extruders. Its controls are created once (index 0) and
// switch_excluder() re-targets them to the extruder selected on m_extruder_switch.
Page* extruder_page() const;
// Config index an "Extruder" page field (e.g. "retraction_length#0") currently edits, -1 if not on that page.
int extruder_page_data_index(const std::string& field_id) const;
// Config index of an extruder's per-variant options (Retraction, Z-Hop, ...): its variant column
// for the nozzle selected on the switch, or the extruder index on a printer without variants.
int extruder_variant_index(int extruder);
// After the config was restored (roll back): follow its extruder count, if it differs.
void sync_extruders_count();
// Search jump to "Extruder N" / "key#N": selects extruder N on the switch, then activates the
// page's own field on the "Extruder" page.
void activate_option(const std::string& opt_key, const wxString& category) override;
protected:
// Orca: values of extruders added by raising the extruder count have no saved / system value to
// revert to; the change is shown on "extruders_count", not on each of their parameters.
void update_custom_dirty(std::vector<std::string> &dirty_options, std::vector<std::string> &nonsys_options) override;
}; };
class TabSLAMaterial : public Tab class TabSLAMaterial : public Tab
+17 -6
View File
@@ -25,11 +25,19 @@ extern const char* const INSTALL_STATE_FILE;
// Plugin config and orca.host.ui payloads both cross the boundary as plain JSON-compatible // Plugin config and orca.host.ui payloads both cross the boundary as plain JSON-compatible
// values, so both go through these. // values, so both go through these.
inline pybind11::object json_to_py(const nlohmann::json& j) // Maximum nesting depth for JSON <-> Python conversion. A self-referential or pathologically
// deep value would otherwise recurse until the native C stack overflows, an uncatchable crash;
// past this bound we raise instead. 200 is far beyond any legitimate plugin config or UI payload.
inline constexpr int kMaxJsonConversionDepth = 200;
inline pybind11::object json_to_py(const nlohmann::json& j, int depth = 0)
{ {
namespace py = pybind11; namespace py = pybind11;
using json = nlohmann::json; using json = nlohmann::json;
if (depth > kMaxJsonConversionDepth)
throw py::value_error("Plugin JSON value nested too deeply");
switch (j.type()) { switch (j.type()) {
case json::value_t::null: return py::none(); case json::value_t::null: return py::none();
case json::value_t::boolean: return py::bool_(j.get<bool>()); case json::value_t::boolean: return py::bool_(j.get<bool>());
@@ -40,24 +48,27 @@ inline pybind11::object json_to_py(const nlohmann::json& j)
case json::value_t::array: { case json::value_t::array: {
py::list lst; py::list lst;
for (const auto& e : j) for (const auto& e : j)
lst.append(json_to_py(e)); lst.append(json_to_py(e, depth + 1));
return lst; return lst;
} }
case json::value_t::object: { case json::value_t::object: {
py::dict d; py::dict d;
for (auto it = j.begin(); it != j.end(); ++it) for (auto it = j.begin(); it != j.end(); ++it)
d[py::str(it.key())] = json_to_py(it.value()); d[py::str(it.key())] = json_to_py(it.value(), depth + 1);
return d; return d;
} }
default: return py::none(); default: return py::none();
} }
} }
inline nlohmann::json py_to_json(const pybind11::handle& o) inline nlohmann::json py_to_json(const pybind11::handle& o, int depth = 0)
{ {
namespace py = pybind11; namespace py = pybind11;
using json = nlohmann::json; using json = nlohmann::json;
if (depth > kMaxJsonConversionDepth)
throw py::value_error("Plugin value nested too deeply (possible cycle)");
if (o.is_none()) if (o.is_none())
return json(nullptr); return json(nullptr);
if (py::isinstance<py::bool_>(o)) // bool before int (bool subclasses int in Python) if (py::isinstance<py::bool_>(o)) // bool before int (bool subclasses int in Python)
@@ -73,13 +84,13 @@ inline nlohmann::json py_to_json(const pybind11::handle& o)
if (py::isinstance<py::dict>(o)) { if (py::isinstance<py::dict>(o)) {
json obj = json::object(); json obj = json::object();
for (auto item : py::reinterpret_borrow<py::dict>(o)) for (auto item : py::reinterpret_borrow<py::dict>(o))
obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second); obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second, depth + 1);
return obj; return obj;
} }
if (py::isinstance<py::list>(o) || py::isinstance<py::tuple>(o)) { if (py::isinstance<py::list>(o) || py::isinstance<py::tuple>(o)) {
json arr = json::array(); json arr = json::array();
for (auto e : o) for (auto e : o)
arr.push_back(py_to_json(e)); arr.push_back(py_to_json(e, depth + 1));
return arr; return arr;
} }
return py::str(o).cast<std::string>(); // fallback: str() return py::str(o).cast<std::string>(); // fallback: str()
+5 -43
View File
@@ -28,7 +28,6 @@
#include <wx/app.h> #include <wx/app.h>
#include <wx/defs.h> #include <wx/defs.h>
#include <wx/thread.h> #include <wx/thread.h>
#include <wx/timer.h>
#include <wx/toplevel.h> #include <wx/toplevel.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/progdlg.h> #include <wx/progdlg.h>
@@ -416,44 +415,6 @@ struct UiDockPanelHandle
int id{0}; int id{0};
}; };
// Polls until the plater is shown on screen, then runs the callback with it (null when the app
// is closing instead) and deletes itself. A plugin loaded at startup opens its panes from
// on_load, before the main window exists, and a pane added before that window is laid out is
// sized against the unsized frame and keeps that width (AuiMgr::track_docked_size).
class PlaterShownWaiter : public wxTimer
{
public:
explicit PlaterShownWaiter(std::function<void(GUI::Plater*)> fn) : m_fn(std::move(fn)) {}
// True once the callback ran.
bool try_run()
{
const bool closing = GUI::wxGetApp().is_closing();
GUI::Plater* plater = closing ? nullptr : GUI::wxGetApp().plater();
if (!closing && (plater == nullptr || !plater->IsShownOnScreen()))
return false;
Stop();
m_fn(plater);
// Off the timer callback's stack: wxGTK's timeout callback still reads the timer after Notify().
GUI::wxGetApp().CallAfter([this]() { delete this; });
return true;
}
void Notify() override { try_run(); }
private:
std::function<void(GUI::Plater*)> m_fn;
};
void run_when_plater_shown(std::function<void(GUI::Plater*)> fn)
{
GUI::wxGetApp().CallAfter([fn = std::move(fn)]() mutable {
auto* waiter = new PlaterShownWaiter(std::move(fn));
if (!waiter->try_run())
waiter->Start(100);
});
}
py::object ui_create_dock_panel(const std::string& html, const std::string& title, int width, int height, py::object ui_create_dock_panel(const std::string& html, const std::string& title, int width, int height,
py::object on_message, py::object on_close, const std::string& dock) py::object on_message, py::object on_close, const std::string& dock)
{ {
@@ -473,13 +434,14 @@ py::object ui_create_dock_panel(const std::string& html, const std::string& titl
const int new_id = UiRegistry::instance().reserve_id(); const int new_id = UiRegistry::instance().reserve_id();
UiRegistry::instance().bind(new_id, nullptr, plugin_key); UiRegistry::instance().bind(new_id, nullptr, plugin_key);
run_when_plater_shown([new_id, plugin_key, html, title, dock, w, h, GUI::wxGetApp().CallAfter([new_id, plugin_key, html, title, dock, w, h,
msg_adapter = std::move(msg_adapter), msg_adapter = std::move(msg_adapter),
close_holder = std::move(close_holder)](GUI::Plater* plater) mutable { close_holder = std::move(close_holder)]() mutable {
if (!UiRegistry::instance().is_open(new_id)) if (!UiRegistry::instance().is_open(new_id))
return; return;
if (plater == nullptr) { GUI::Plater* plater = GUI::wxGetApp().plater();
if (plater == nullptr || GUI::wxGetApp().is_closing()) {
UiRegistry::instance().remove(new_id); UiRegistry::instance().remove(new_id);
return; return;
} }
@@ -800,43 +800,6 @@ TEST_CASE("A per-variant filament option read with a single value gives it to ev
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021})); REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
} }
// Machine limits (printer_options_with_variant_2) hold a (normal, silent) pair per printer variant, so the
// printer Tab's Motion ability page can edit each extruder's own limits.
TEST_CASE("set_num_extruders gives every printer variant its own pair of machine limits", "[Config]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
auto speed_x = [&config]() { return config.option<ConfigOptionFloats>("machine_max_speed_x")->values; };
SECTION("a new extruder's pair is padded with the first value, like the other variant keys") {
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200.};
config.set_num_extruders(3);
REQUIRE(config.option<ConfigOptionStrings>("printer_extruder_variant")->size() == 3);
REQUIRE(speed_x() == std::vector<double>({500., 200., 500., 500., 500., 500.}));
}
SECTION("per-extruder pairs are kept, and removing an extruder removes its pair") {
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200., 400., 150., 300., 100.};
config.set_num_extruders(3);
REQUIRE(speed_x() == std::vector<double>({500., 200., 400., 150., 300., 100.}));
config.set_num_extruders(2);
REQUIRE(speed_x() == std::vector<double>({500., 200., 400., 150.}));
}
SECTION("a printer with nozzle variants gets a pair per variant") {
// 2 extruders x 2 variants each = 4 variants
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow"};
const std::vector<double> per_variant = {500., 200., 510., 210., 520., 220., 530., 230.};
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = per_variant;
config.set_num_extruders(2);
REQUIRE(speed_x() == per_variant);
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200.};
config.set_num_extruders(2);
REQUIRE(speed_x() == std::vector<double>({500., 200., 500., 500., 500., 500., 500., 500.}));
}
}
// The device drying options hold several values per filament, as many as each filament preset gives. // The device drying options hold several values per filament, as many as each filament preset gives.
TEST_CASE("The device drying options are rebuilt as each filament's values in slot order", "[Config]") TEST_CASE("The device drying options are rebuilt as each filament's values in slot order", "[Config]")
{ {
+1
View File
@@ -34,6 +34,7 @@ add_executable(${_TEST_NAME}_tests
test_plugin_sort.cpp test_plugin_sort.cpp
test_plugin_cloud_metadata.cpp test_plugin_cloud_metadata.cpp
test_plugin_audit.cpp test_plugin_audit.cpp
test_plugin_json_depth.cpp
test_shortcuts.cpp test_shortcuts.cpp
test_file_url.cpp test_file_url.cpp
test_user_manager.cpp test_user_manager.cpp
@@ -0,0 +1,73 @@
#include <catch2/catch_all.hpp>
#include <catch2/catch_test_macros.hpp>
#include <slic3r/plugin/PluginFsUtils.hpp>
#include <slic3r/plugin/PluginManager.hpp>
#include <slic3r/plugin/PythonInterpreter.hpp>
#include "plugin_test_utils.hpp"
#include <cstdint>
#include <exception>
#include <utility>
#include <nlohmann/json.hpp>
#include <pybind11/embed.h>
#include <pybind11/gil.h>
#include <pybind11/pytypes.h>
using namespace Slic3r;
namespace {
// Brings the embedded interpreter up for one test and tears it down before boost::log does,
// mirroring the ScopedPluginManager idiom in the other plugin tests.
struct ScopedPluginManager
{
ScopedDataDir python_data_dir{"plugin-json-depth"};
bool initialized = PluginManager::instance().initialize();
~ScopedPluginManager()
{
PluginManager::instance().shutdown();
PythonInterpreter::instance().shutdown();
}
};
} // namespace
TEST_CASE("py_to_json raises instead of overflowing on pathologically deep input", "[PluginHost][Python]")
{
ScopedPluginManager manager;
REQUIRE(manager.initialized);
namespace py = pybind11;
py::gil_scoped_acquire gil;
// [[[ ... 0 ... ]]] nested 300 deep: past the 200 conversion-depth cap, but shallow enough
// that the pre-fix code returns without crashing, so a regression fails cleanly rather than
// taking the process down. Built in C++ so the test does not depend on Python builtins.
py::object deep = py::int_(0);
for (int i = 0; i < 300; ++i) {
py::list wrapper;
wrapper.append(deep);
deep = std::move(wrapper);
}
CHECK_THROWS_AS(py_to_json(deep), std::exception);
}
TEST_CASE("py_to_json still converts reasonably nested input", "[PluginHost][Python]")
{
ScopedPluginManager manager;
REQUIRE(manager.initialized);
namespace py = pybind11;
py::gil_scoped_acquire gil;
py::dict d;
d["a"] = py::int_(1);
py::list inner;
inner.append(py::str("x"));
inner.append(py::int_(2));
d["b"] = inner;
const nlohmann::json j = py_to_json(d);
CHECK(j.at("a").get<std::int64_t>() == 1);
CHECK(j.at("b").at(0).get<std::string>() == "x");
CHECK(j.at("b").at(1).get<std::int64_t>() == 2);
}