Merge branch 'main' into feat/plugin-lifecycle-evts

This commit is contained in:
Ian Chua
2026-09-22 14:21:36 +08:00
1052 changed files with 140050 additions and 5263 deletions
+74 -21
View File
@@ -1737,16 +1737,46 @@ void Tab::toggle_option(const std::string& opt_key, bool toggle, int opt_index/*
void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
{
if (!m_active_page) return;
Line *line = m_active_page->get_line(opt_key, opt_index);
if (line) line->toggle_visible = toggle;
// Apply to every page that owns the option, not just m_active_page. ConfigManipulation runs while
// each tab updates at preset load, so the Speed Dial sees the same visibility regardless of page.
for (const PageShp& page : m_pages) {
if (!page) continue;
if (Line *line = page->get_line(opt_key, opt_index))
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);
// Same as toggle_line: a runtime rename (brim_width -> "Brim ear radius") must reach every page
// so the Speed Dial titles the setting before the page has been shown.
for (const PageShp& page : m_pages) {
if (!page) continue;
if (Line *line = page->get_line(opt_key, opt_index))
line->set_label(label);
}
}
Tab::SettingRowState Tab::setting_row_state(const std::string &opt_id) const
{
bool found = false;
for (const PageShp& page : m_pages) {
if (!page) continue;
for (const ConfigOptionsGroupShp& group : page->m_optgroups) {
if (!group) continue;
for (const Line& line : group->get_lines()) {
for (const Option& opt : line.get_options()) {
if (opt.opt_id != opt_id)
continue;
if (line.toggle_visible) // shown on any owning page is enough
return {true, line.label, line.get_options().size() > 1};
found = true;
}
}
}
}
// Never registered on a page -> visible, but with no row label to contribute.
return {!found, wxString(), false};
}
// To be called by custom widgets, load a value into a config,
@@ -3962,7 +3992,7 @@ void TabPrintLayer::update_custom_dirty(std::vector<std::string> &dirty_options,
bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode)
{
std::vector<std::string> tags;
bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags);
bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags, wxGetApp().preset_bundle->is_bbl_vendor());
if (invalid) {
std::string lines = ":\n";
for (const std::string& keyword : tags)
@@ -3991,11 +4021,27 @@ static void validate_custom_gcode_cb(Tab* tab, ConfigOptionsGroupShp opt_group,
tab->on_value_change(opt_key, value);
}
// Orca: names the field the way its page does. The option label alone is ambiguous, as the machine's
// and the filament's custom G-code are both labelled "Start G-code".
static wxString custom_gcode_group_title(const Page* page, const t_config_option_key& opt_key)
{
if (page)
for (const auto& opt_group : page->m_optgroups)
for (const auto& opt : opt_group->opt_map())
if (opt.second.first == opt_key)
return opt_group->title;
return from_u8(opt_key);
}
void Tab::edit_custom_gcode(const t_config_option_key& opt_key)
{
EditGCodeDialog dlg = EditGCodeDialog(this, opt_key, get_custom_gcode(opt_key));
if (dlg.ShowModal() == wxID_OK) {
set_custom_gcode(opt_key, dlg.get_edited_gcode());
const std::string edited_gcode = dlg.get_edited_gcode();
// Orca: this dialog writes the value straight into the config, bypassing the field's change
// handler, so the reserved keyword check has to run here as it does when editing in place.
validate_custom_gcodes_was_shown = !validate_custom_gcode(custom_gcode_group_title(m_active_page, opt_key), edited_gcode);
set_custom_gcode(opt_key, edited_gcode);
update_dirty();
update();
}
@@ -5783,11 +5829,11 @@ if (is_marlin_flavor)
} else if (m_extruders_count_old == 1) {
first_extruder_title = wxString::Format("Extruder %d", 1);
}
auto & searcher = wxGetApp().sidebar().get_searcher();
auto & index = wxGetApp().sidebar().settings_index();
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())
searcher.add_key(opt.first + "#0", m_type, group->title, first_extruder_title);
index.add_key(opt.first + "#0", m_type, group->title, first_extruder_title, group->icon);
}
Thaw();
@@ -7885,10 +7931,10 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent)
}));
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
const Search::GroupAndCategory& gc = searcher.get_group_and_category("printable_area");
searcher.add_key("bed_custom_texture", m_type, gc.group, gc.category);
searcher.add_key("bed_custom_model", m_type, gc.group, gc.category);
Search::SettingsIndex& index = wxGetApp().sidebar().settings_index();
const Search::GroupAndCategory& gc = index.get_group_and_category("printable_area");
index.add_key("bed_custom_texture", m_type, gc.group, gc.category, gc.icon);
index.add_key("bed_custom_model", m_type, gc.group, gc.category, gc.icon);
}
return sizer;
@@ -8371,17 +8417,19 @@ void Tab::sync_excluder()
Preset & printer_preset = m_preset_bundle->printers.get_edited_preset();
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type");
// Motion ability options hold a (normal, silent) pair per variant, so switch_excluder indexes that page with stride 2.
const int stride = m_active_page->title().StartsWith("Motion ability") ? 2 : 1;
auto get_index_for_extruder =
[this, &extruders, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, NozzleVolumeType nozzle_type) {
[this, &extruders, stride, variant_keys = extruder_variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, NozzleVolumeType nozzle_type) {
return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first,
ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second);
ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second, stride);
};
int active_index = get_current_active_extruder();
auto active_nozzle = get_actual_nozzle_volume_type(active_index);
int from_index = get_index_for_extruder(active_index, active_nozzle);
int dest_index = get_index_for_extruder(1 - active_index, active_nozzle);
auto from_str = std::to_string(from_index);
auto dest_str = std::to_string(dest_index);
if (from_index < 0 || dest_index < 0) // no variant column for this nozzle on one of the extruders
return;
auto dirty_options = m_presets->current_dirty_options(true);
DynamicConfig config_origin, config_to_apply;
for (int i = 0; i < dirty_options.size(); ++i) {
@@ -8394,16 +8442,21 @@ void Tab::sync_excluder()
if (field == nullptr || line == nullptr)
continue;
++n;
bool dirty = opt.substr(n) == from_str;
auto is_from_slot = [&](const std::string &dirty_opt) {
int slot = std::atoi(dirty_opt.c_str() + n);
return slot >= from_index && slot < from_index + stride;
};
bool dirty = is_from_slot(opt);
while (i + 1 < dirty_options.size() && dirty_options[i + 1].compare(0, n, opt, 0, n) == 0) {
dirty |= dirty_options[i + 1].substr(n) == from_str;
dirty |= is_from_slot(dirty_options[i + 1]);
++i;
}
if (dirty) {
auto key = opt.substr(0, n - 1);
auto option = dynamic_cast<ConfigOptionVectorBase*>(m_config->option(key));
auto option2 = dynamic_cast<ConfigOptionVectorBase*>(option->clone());
option2->set_at(option, dest_index, from_index);
for (int s = 0; s < stride; ++s)
option2->set_at(option, dest_index + s, from_index + s);
if (*option == *option2) {
delete option2;
continue;