mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
944 lines
38 KiB
C++
944 lines
38 KiB
C++
#include "ColorDecomposeDialog.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <wx/sizer.h>
|
|
#include <wx/dcclient.h>
|
|
#include <wx/dcbuffer.h>
|
|
#include "wx/graphics.h"
|
|
|
|
#include "I18N.hpp"
|
|
#include "GUI.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "format.hpp"
|
|
#include "Widgets/ComboBox.hpp"
|
|
#include "Widgets/DropDown.hpp"
|
|
#include "Widgets/Button.hpp"
|
|
#include "Widgets/CheckBox.hpp"
|
|
#include "Widgets/Label.hpp"
|
|
#include "wxExtensions.hpp"
|
|
#include "ColorDecomposeSupport.hpp"
|
|
#include "libslic3r/ColorDecomposeRecipe.hpp"
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
static const wxColour COLOR_BRAND("#00AE42");
|
|
static const wxColour COLOR_BORDER_NORMAL("#EEEEEE");
|
|
static const wxColour COLOR_BG_CARD("#F8F8F8");
|
|
static const wxColour COLOR_LABEL_GREY("#ACACAC");
|
|
static const wxColour COLOR_TEXT_DARK("#262E30");
|
|
static const wxColour COLOR_DIVIDER("#EEEEEE");
|
|
|
|
// Standard CMYW base colors
|
|
static const wxColour CMYW_CYAN(0, 255, 255);
|
|
static const wxColour CMYW_MAGENTA(255, 0, 255);
|
|
static const wxColour CMYW_YELLOW(255, 255, 0);
|
|
static const wxColour CMYW_WHITE(255, 255, 255);
|
|
|
|
// Standard RYBW base colors
|
|
static const wxColour RYBW_RED(255, 0, 0);
|
|
static const wxColour RYBW_YELLOW(255, 255, 0);
|
|
static const wxColour RYBW_BLUE(0, 0, 255);
|
|
static const wxColour RYBW_WHITE(255, 255, 255);
|
|
|
|
static size_t mode_index(DecomposeMode mode)
|
|
{
|
|
return static_cast<size_t>(mode);
|
|
}
|
|
|
|
static ColorDecomposeRgb wx_colour_to_recipe_rgb(const wxColour& color)
|
|
{
|
|
return {
|
|
static_cast<unsigned char>(color.Red()),
|
|
static_cast<unsigned char>(color.Green()),
|
|
static_cast<unsigned char>(color.Blue())
|
|
};
|
|
}
|
|
|
|
static wxColour hex_to_wx_colour(const std::string& hex, const wxColour& fallback)
|
|
{
|
|
wxColour color(hex);
|
|
return color.IsOk() ? color : fallback;
|
|
}
|
|
|
|
static bool same_rgb(const wxColour& lhs, const wxColour& rhs)
|
|
{
|
|
return lhs.Red() == rhs.Red() && lhs.Green() == rhs.Green() && lhs.Blue() == rhs.Blue();
|
|
}
|
|
|
|
static DecomposeBaseColor standard_base_color_from_key(const std::string& key)
|
|
{
|
|
if (key == "Cyan") return DecomposeBaseColor::Cyan;
|
|
if (key == "Magenta") return DecomposeBaseColor::Magenta;
|
|
if (key == "Yellow") return DecomposeBaseColor::Yellow;
|
|
if (key == "White") return DecomposeBaseColor::White;
|
|
if (key == "Red") return DecomposeBaseColor::Red;
|
|
if (key == "Green") return DecomposeBaseColor::Green;
|
|
if (key == "Blue") return DecomposeBaseColor::Blue;
|
|
return DecomposeBaseColor::None;
|
|
}
|
|
|
|
static wxColour pure_color_for_base(DecomposeBaseColor base)
|
|
{
|
|
switch (base) {
|
|
case DecomposeBaseColor::Cyan: return CMYW_CYAN;
|
|
case DecomposeBaseColor::Magenta: return CMYW_MAGENTA;
|
|
case DecomposeBaseColor::Yellow: return CMYW_YELLOW;
|
|
case DecomposeBaseColor::White: return CMYW_WHITE;
|
|
case DecomposeBaseColor::Red: return RYBW_RED;
|
|
case DecomposeBaseColor::Blue: return RYBW_BLUE;
|
|
default: return *wxBLACK;
|
|
}
|
|
}
|
|
|
|
static DecomposeBaseColor standard_base_color_for(DecomposeMode mode, const wxColour& color)
|
|
{
|
|
if (mode == DecomposeMode::CMYW) {
|
|
if (same_rgb(color, CMYW_CYAN)) return DecomposeBaseColor::Cyan;
|
|
if (same_rgb(color, CMYW_MAGENTA)) return DecomposeBaseColor::Magenta;
|
|
if (same_rgb(color, CMYW_YELLOW)) return DecomposeBaseColor::Yellow;
|
|
if (same_rgb(color, CMYW_WHITE)) return DecomposeBaseColor::White;
|
|
} else if (mode == DecomposeMode::RYBW) {
|
|
if (same_rgb(color, RYBW_RED)) return DecomposeBaseColor::Red;
|
|
if (same_rgb(color, RYBW_YELLOW)) return DecomposeBaseColor::Yellow;
|
|
if (same_rgb(color, RYBW_BLUE)) return DecomposeBaseColor::Blue;
|
|
if (same_rgb(color, RYBW_WHITE)) return DecomposeBaseColor::White;
|
|
}
|
|
return DecomposeBaseColor::None;
|
|
}
|
|
|
|
static ColorDecomposeResult to_dialog_result(const ColorDecomposeRecipeResult& recipe,
|
|
const wxColour& fallback)
|
|
{
|
|
ColorDecomposeResult result;
|
|
result.mode = recipe.mode;
|
|
result.matched_color = hex_to_wx_colour(recipe.matched_color_hex, fallback);
|
|
for (const auto& comp_recipe : recipe.components) {
|
|
DecomposeComponent comp;
|
|
comp.colour = hex_to_wx_colour(comp_recipe.color_hex, fallback);
|
|
comp.ratio = comp_recipe.ratio;
|
|
comp.filament_index = static_cast<int>(comp_recipe.filament_index);
|
|
comp.base_color = standard_base_color_from_key(comp_recipe.base_color);
|
|
if (comp.base_color == DecomposeBaseColor::None)
|
|
comp.base_color = standard_base_color_for(recipe.mode, comp.colour);
|
|
result.components.push_back(comp);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static wxPanel* create_h_divider(wxWindow* parent, int fixed_width = -1)
|
|
{
|
|
const int h = parent->FromDIP(1);
|
|
int w = fixed_width > 0 ? fixed_width : -1;
|
|
auto* panel = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(w, h));
|
|
panel->SetMinSize(wxSize(w, h));
|
|
if (fixed_width > 0)
|
|
panel->SetMaxSize(wxSize(fixed_width, h));
|
|
panel->SetBackgroundColour(StateColor::darkModeColorFor(COLOR_DIVIDER));
|
|
return panel;
|
|
}
|
|
|
|
static wxStaticText* create_mode_group_label(wxWindow* parent, const wxString& text)
|
|
{
|
|
auto* label = new wxStaticText(parent, wxID_ANY, text);
|
|
label->SetFont(Label::Body_11);
|
|
label->SetForegroundColour(StateColor::darkModeColorFor(COLOR_LABEL_GREY));
|
|
return label;
|
|
}
|
|
|
|
static void match_parent_bg(wxWindow* w, const wxColour& bg)
|
|
{
|
|
w->SetBackgroundColour(bg);
|
|
}
|
|
|
|
static bool material_type_matches(const std::string& a, const std::string& b)
|
|
{
|
|
if (a.empty() || b.empty())
|
|
return false;
|
|
return a == b || a == b + " Basic" || b == a + " Basic";
|
|
}
|
|
|
|
|
|
ColorDecomposeDialog::ColorDecomposeDialog(wxWindow* parent,
|
|
int filament_idx,
|
|
const wxColour& target_color,
|
|
const std::vector<std::string>& physical_colors,
|
|
const std::vector<std::string>& filament_names,
|
|
const std::vector<std::string>& filament_types,
|
|
size_t current_filament_count,
|
|
size_t max_filament_count,
|
|
std::vector<size_t> physical_config_indices)
|
|
: DPIDialog(parent, wxID_ANY, _L("Decompose Color"), wxDefaultPosition,
|
|
wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
|
, m_filament_idx(filament_idx)
|
|
, m_target_color(target_color)
|
|
, m_physical_colors(physical_colors)
|
|
, m_filament_names(filament_names)
|
|
, m_filament_types(filament_types)
|
|
, m_current_filament_count(current_filament_count)
|
|
, m_max_filament_count(max_filament_count)
|
|
, m_physical_config_indices(std::move(physical_config_indices))
|
|
{
|
|
for (const auto& t : m_filament_types) {
|
|
if (std::find(m_project_types.begin(), m_project_types.end(), t) == m_project_types.end())
|
|
m_project_types.push_back(t);
|
|
}
|
|
|
|
if (m_filament_idx >= 0 && static_cast<size_t>(m_filament_idx) < m_filament_types.size())
|
|
m_preferred_type = m_filament_types[m_filament_idx];
|
|
else if (!m_project_types.empty())
|
|
m_preferred_type = m_project_types.front();
|
|
|
|
build_ui();
|
|
wxGetApp().UpdateDlgDarkUI(this);
|
|
// Restore target swatch after dark mode color remapping
|
|
if (m_target_swatch)
|
|
m_target_swatch->SetBackgroundColour(m_target_color);
|
|
|
|
update_card_visibility();
|
|
Fit();
|
|
compute_decomposition();
|
|
update_matched_color_display();
|
|
update_ok_button_state();
|
|
}
|
|
|
|
void ColorDecomposeDialog::on_dpi_changed(const wxRect& suggested_rect)
|
|
{
|
|
(void)suggested_rect;
|
|
Fit();
|
|
Refresh();
|
|
}
|
|
|
|
void ColorDecomposeDialog::build_ui()
|
|
{
|
|
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
|
|
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
const int selector_side_margin = FromDIP(26);
|
|
const int selector_top_gap = FromDIP(22);
|
|
const int content_side_margin = FromDIP(30);
|
|
const int target_section_top_gap = FromDIP(18);
|
|
|
|
main_sizer->AddSpacer(selector_top_gap);
|
|
main_sizer->Add(create_filament_selector(), 0, wxEXPAND | wxLEFT | wxRIGHT, selector_side_margin);
|
|
main_sizer->AddSpacer(target_section_top_gap);
|
|
main_sizer->Add(create_target_color_section(), 0, wxEXPAND | wxLEFT | wxRIGHT, content_side_margin);
|
|
main_sizer->AddSpacer(FromDIP(16));
|
|
main_sizer->Add(create_h_divider(this), 0, wxEXPAND | wxLEFT | wxRIGHT, content_side_margin);
|
|
main_sizer->AddSpacer(FromDIP(16));
|
|
main_sizer->Add(create_mode_selection_section(), 0, wxEXPAND | wxLEFT | wxRIGHT, content_side_margin);
|
|
main_sizer->AddSpacer(FromDIP(16));
|
|
main_sizer->Add(create_button_panel(), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, content_side_margin);
|
|
|
|
SetSizer(main_sizer);
|
|
SetMinSize(wxSize(FromDIP(477), FromDIP(380)));
|
|
Fit();
|
|
CenterOnParent();
|
|
}
|
|
|
|
wxBoxSizer* ColorDecomposeDialog::create_filament_selector()
|
|
{
|
|
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
m_type_combo = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
|
wxSize(-1, FromDIP(36)), 0, nullptr, wxCB_READONLY);
|
|
m_type_combo->SetFont(Label::Body_13);
|
|
|
|
m_combo_item_types.clear();
|
|
int default_sel = -1;
|
|
|
|
// --- Group 1: Project filament list (deduplicated by type) ---
|
|
m_type_combo->Append(_L("Project Filament List"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
|
m_combo_item_types.push_back(std::string());
|
|
|
|
std::set<std::string> seen_types;
|
|
for (size_t i = 0; i < m_filament_names.size(); ++i) {
|
|
const std::string& type = (i < m_filament_types.size()) ? m_filament_types[i] : "PLA";
|
|
if (!seen_types.insert(type).second)
|
|
continue;
|
|
int idx = m_type_combo->Append(wxString::FromUTF8(m_filament_names[i]));
|
|
m_combo_item_types.push_back(type);
|
|
if (type == m_preferred_type && default_sel < 0)
|
|
default_sel = idx;
|
|
}
|
|
|
|
// --- Group 2: Standard mode material recommendations ---
|
|
static const char* kStandardTypes[] = {
|
|
kDecomposePlaBasicType
|
|
};
|
|
|
|
m_type_combo->Append(_L("Standard Mode Recommendations"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
|
m_combo_item_types.push_back(std::string());
|
|
|
|
for (size_t s = 0; s < sizeof(kStandardTypes) / sizeof(kStandardTypes[0]); ++s) {
|
|
// Always show standard recommendations, even if the same type already
|
|
// appears in the project filament list above.
|
|
const std::string label = std::string(kDecomposeBambuPresetPrefix) + kStandardTypes[s];
|
|
int idx = m_type_combo->Append(wxString::FromUTF8(label));
|
|
m_combo_item_types.push_back(kStandardTypes[s]);
|
|
if (kStandardTypes[s] == m_preferred_type && default_sel < 0)
|
|
default_sel = idx;
|
|
}
|
|
|
|
if (default_sel < 0) {
|
|
for (int i = 0; i < static_cast<int>(m_combo_item_types.size()); ++i) {
|
|
if (!m_combo_item_types[i].empty()) {
|
|
default_sel = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (default_sel >= 0) {
|
|
m_type_combo->SetSelection(default_sel);
|
|
if (!m_combo_item_types[default_sel].empty())
|
|
m_preferred_type = m_combo_item_types[default_sel];
|
|
}
|
|
|
|
m_type_combo->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
|
evt.StopPropagation();
|
|
int sel = m_type_combo->GetSelection();
|
|
if (sel >= 0 && static_cast<size_t>(sel) < m_combo_item_types.size()
|
|
&& !m_combo_item_types[sel].empty()) {
|
|
m_preferred_type = m_combo_item_types[sel];
|
|
}
|
|
update_card_visibility();
|
|
compute_decomposition();
|
|
update_matched_color_display();
|
|
update_ok_button_state();
|
|
});
|
|
|
|
sizer->Add(m_type_combo, 1, wxEXPAND);
|
|
return sizer;
|
|
}
|
|
|
|
static wxPanel* create_color_swatch(wxWindow* parent, const wxColour& color, int size)
|
|
{
|
|
auto* panel = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(size, size));
|
|
panel->SetBackgroundColour(color);
|
|
panel->SetMinSize(wxSize(size, size));
|
|
return panel;
|
|
}
|
|
|
|
wxBoxSizer* ColorDecomposeDialog::create_target_color_section()
|
|
{
|
|
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
auto* label = new wxStaticText(this, wxID_ANY, _L("Target Color"));
|
|
label->SetFont(Label::Head_14);
|
|
label->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(19));
|
|
|
|
m_target_swatch = create_color_swatch(this, m_target_color, FromDIP(28));
|
|
sizer->Add(m_target_swatch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(12));
|
|
|
|
m_target_rgb_text = new wxStaticText(this, wxID_ANY,
|
|
wxString::Format("RGB: %d, %d, %d", m_target_color.Red(), m_target_color.Green(), m_target_color.Blue()));
|
|
m_target_rgb_text->SetFont(Label::Body_13);
|
|
m_target_rgb_text->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
sizer->Add(m_target_rgb_text, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(12));
|
|
|
|
auto* arrow_text = new wxStaticText(this, wxID_ANY, wxString::FromUTF8("\xe2\x86\x92"));
|
|
arrow_text->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
sizer->Add(arrow_text, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(12));
|
|
|
|
m_matched_swatch = create_color_swatch(this, m_target_color, FromDIP(28));
|
|
sizer->Add(m_matched_swatch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(12));
|
|
|
|
m_matched_rgb_text = new wxStaticText(this, wxID_ANY,
|
|
wxString::Format("RGB: %d, %d, %d", m_target_color.Red(), m_target_color.Green(), m_target_color.Blue()));
|
|
m_matched_rgb_text->SetFont(Label::Head_13);
|
|
m_matched_rgb_text->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
sizer->Add(m_matched_rgb_text, 0, wxALIGN_CENTER_VERTICAL);
|
|
|
|
return sizer;
|
|
}
|
|
|
|
wxPanel* ColorDecomposeDialog::create_mode_card(wxWindow* parent, DecomposeMode mode,
|
|
const wxString& title)
|
|
{
|
|
const int pad = FromDIP(12);
|
|
|
|
auto* card = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
|
card->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
|
|
|
auto* card_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
auto* title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
auto* title_label = new wxStaticText(card, wxID_ANY, title);
|
|
title_label->SetFont(Label::Body_14);
|
|
title_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#909090")));
|
|
match_parent_bg(title_label, StateColor::darkModeColorFor(COLOR_BG_CARD));
|
|
title_sizer->Add(title_label, 1, wxALIGN_CENTER_VERTICAL);
|
|
|
|
auto* chk = new ::CheckBox(card);
|
|
chk->SetValue(mode == m_selected_mode);
|
|
match_parent_bg(chk, StateColor::darkModeColorFor(COLOR_BG_CARD));
|
|
switch (mode) {
|
|
case DecomposeMode::MaterialList: m_chk_material_list = chk; break;
|
|
case DecomposeMode::CMYW: m_chk_cmyw = chk; break;
|
|
case DecomposeMode::RYBW: m_chk_rybw = chk; break;
|
|
}
|
|
chk->Bind(wxEVT_TOGGLEBUTTON, [this, mode](wxCommandEvent& e) {
|
|
select_mode(mode);
|
|
e.Skip(); // let CheckBox::update() re-sync its bitmap to GetValue()
|
|
});
|
|
title_sizer->Add(chk, 0, wxALIGN_CENTER_VERTICAL);
|
|
|
|
card_sizer->Add(title_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, pad);
|
|
|
|
card_sizer->Add(create_h_divider(card), 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(8));
|
|
|
|
auto* colors_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
card_sizer->Add(colors_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, pad);
|
|
|
|
auto& controls = m_mode_cards[mode_index(mode)];
|
|
controls.card = card;
|
|
controls.components_sizer = colors_sizer;
|
|
|
|
card->SetSizer(card_sizer);
|
|
card->SetMinSize(wxSize(FromDIP(128), FromDIP(111)));
|
|
card->SetMaxSize(wxSize(FromDIP(128), FromDIP(111)));
|
|
|
|
card->Bind(wxEVT_PAINT, [this, card, mode](wxPaintEvent&) {
|
|
wxBufferedPaintDC dc(card);
|
|
wxSize sz = card->GetClientSize();
|
|
dc.SetBackground(wxBrush(StateColor::darkModeColorFor(*wxWHITE)));
|
|
dc.Clear();
|
|
|
|
bool selected = (m_selected_mode == mode);
|
|
wxColour border_col = selected
|
|
? StateColor::darkModeColorFor(COLOR_BRAND)
|
|
: StateColor::darkModeColorFor(COLOR_BORDER_NORMAL);
|
|
const int border_width = FromDIP(selected ? 2 : 1);
|
|
const double inset = border_width / 2.0;
|
|
std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
|
|
if (gc) {
|
|
gc->SetPen(wxPen(border_col, border_width));
|
|
gc->SetBrush(wxBrush(StateColor::darkModeColorFor(COLOR_BG_CARD)));
|
|
gc->DrawRoundedRectangle(inset, inset, sz.x - 2 * inset, sz.y - 2 * inset, FromDIP(8));
|
|
} else {
|
|
const int fallback_inset = (border_width + 1) / 2;
|
|
dc.SetPen(wxPen(border_col, border_width));
|
|
dc.SetBrush(wxBrush(StateColor::darkModeColorFor(COLOR_BG_CARD)));
|
|
dc.DrawRoundedRectangle(fallback_inset, fallback_inset, sz.x - 2 * fallback_inset, sz.y - 2 * fallback_inset, FromDIP(8));
|
|
}
|
|
});
|
|
|
|
std::function<void(wxWindow*)> bind_click;
|
|
bind_click = [this, mode, chk, &bind_click](wxWindow* w) {
|
|
if (w == chk || dynamic_cast<::CheckBox*>(w))
|
|
return;
|
|
w->Bind(wxEVT_LEFT_UP, [this, mode](wxMouseEvent&) {
|
|
select_mode(mode);
|
|
});
|
|
w->SetCursor(wxCursor(wxCURSOR_HAND));
|
|
for (auto* child : w->GetChildren())
|
|
bind_click(child);
|
|
};
|
|
bind_click(card);
|
|
|
|
return card;
|
|
}
|
|
|
|
wxBoxSizer* ColorDecomposeDialog::create_mode_selection_section()
|
|
{
|
|
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
auto* section_label = new wxStaticText(this, wxID_ANY, _L("Select Color Decomposition"));
|
|
section_label->SetFont(Label::Head_14);
|
|
section_label->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
sizer->Add(section_label, 0, wxBOTTOM, FromDIP(4));
|
|
|
|
auto* modes_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
// --- Arbitrary mode column (wrapped in a panel so the whole column hides together) ---
|
|
m_arb_column_panel = new wxPanel(this, wxID_ANY);
|
|
m_arb_column_panel->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
auto* arb_col = new wxBoxSizer(wxVERTICAL);
|
|
{
|
|
auto* arb_header_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
arb_header_sizer->Add(create_mode_group_label(m_arb_column_panel, _L("Arbitrary Mode")),
|
|
0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5));
|
|
arb_header_sizer->Add(create_h_divider(m_arb_column_panel, FromDIP(88)), 0, wxALIGN_CENTER_VERTICAL);
|
|
arb_col->Add(arb_header_sizer, 0, wxEXPAND | wxBOTTOM, FromDIP(8));
|
|
|
|
m_card_material_list = create_mode_card(m_arb_column_panel, DecomposeMode::MaterialList,
|
|
_L("Material List"));
|
|
arb_col->Add(m_card_material_list, 0, wxEXPAND);
|
|
}
|
|
m_arb_column_panel->SetSizer(arb_col);
|
|
modes_sizer->Add(m_arb_column_panel, 0, wxEXPAND | wxRIGHT, FromDIP(16));
|
|
|
|
// --- Standard mode column ---
|
|
auto* std_col = new wxBoxSizer(wxVERTICAL);
|
|
{
|
|
auto* std_header_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
std_header_sizer->Add(create_mode_group_label(this, _L("Standard Mode")),
|
|
0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5));
|
|
std_header_sizer->Add(create_h_divider(this), 1, wxALIGN_CENTER_VERTICAL);
|
|
std_col->Add(std_header_sizer, 0, wxEXPAND | wxBOTTOM, FromDIP(8));
|
|
|
|
auto* cards_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
m_card_cmyw = create_mode_card(this, DecomposeMode::CMYW, "CMYW");
|
|
cards_sizer->Add(m_card_cmyw, 0, wxRIGHT, FromDIP(12));
|
|
|
|
m_card_rybw = create_mode_card(this, DecomposeMode::RYBW, "RYBW");
|
|
cards_sizer->Add(m_card_rybw, 0);
|
|
|
|
std_col->Add(cards_sizer, 0, wxEXPAND);
|
|
}
|
|
modes_sizer->Add(std_col, 0, wxEXPAND);
|
|
|
|
sizer->Add(modes_sizer, 0, wxEXPAND);
|
|
|
|
m_no_card_hint = new wxStaticText(this, wxID_ANY,
|
|
_L("At least two filaments of the same material type are required for decomposition"));
|
|
m_no_card_hint->SetFont(Label::Body_13);
|
|
m_no_card_hint->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#909090")));
|
|
m_no_card_hint->Wrap(FromDIP(400));
|
|
m_no_card_hint->Hide();
|
|
sizer->Add(m_no_card_hint, 0, wxTOP, FromDIP(8));
|
|
|
|
m_limit_warning_panel = new wxPanel(this, wxID_ANY);
|
|
m_limit_warning_panel->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
auto* warning_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
auto* warn_bmp = new wxStaticBitmap(m_limit_warning_panel, wxID_ANY,
|
|
create_scaled_bitmap("obj_warning", m_limit_warning_panel, 16),
|
|
wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16)));
|
|
m_limit_warning_text = new wxStaticText(m_limit_warning_panel, wxID_ANY, wxEmptyString);
|
|
m_limit_warning_text->SetFont(Label::Body_13);
|
|
m_limit_warning_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#D32F2F")));
|
|
m_limit_warning_text->Wrap(FromDIP(400));
|
|
warning_sizer->Add(warn_bmp, 0, wxALIGN_TOP | wxRIGHT, FromDIP(6));
|
|
warning_sizer->Add(m_limit_warning_text, 1, wxEXPAND);
|
|
m_limit_warning_panel->SetSizer(warning_sizer);
|
|
m_limit_warning_panel->Hide();
|
|
sizer->Add(m_limit_warning_panel, 0, wxEXPAND | wxTOP, FromDIP(8));
|
|
|
|
return sizer;
|
|
}
|
|
|
|
wxBoxSizer* ColorDecomposeDialog::create_button_panel()
|
|
{
|
|
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
sizer->AddStretchSpacer();
|
|
|
|
m_btn_cancel = new Button(this, _L("Cancel"));
|
|
m_btn_cancel->SetBackgroundColor(StateColor::darkModeColorFor(*wxWHITE));
|
|
m_btn_cancel->SetBorderColor(StateColor::darkModeColorFor(wxColour("#CECECE")));
|
|
m_btn_cancel->SetTextColor(StateColor::darkModeColorFor(wxColour("#262E30")));
|
|
m_btn_cancel->SetMinSize(wxSize(FromDIP(55), FromDIP(24)));
|
|
m_btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
|
|
|
m_btn_ok = new Button(this, _L("OK"));
|
|
m_btn_ok->SetBackgroundColor(StateColor(
|
|
std::make_pair(wxColour("#C2C2C2"), (int) StateColor::Disabled),
|
|
std::make_pair(wxColour("#00AE42"), (int) StateColor::Normal)));
|
|
m_btn_ok->SetBorderColor(StateColor(
|
|
std::make_pair(wxColour("#C2C2C2"), (int) StateColor::Disabled),
|
|
std::make_pair(wxColour("#00AE42"), (int) StateColor::Normal)));
|
|
m_btn_ok->SetTextColor(StateColor(
|
|
std::make_pair(*wxWHITE, (int) StateColor::Disabled),
|
|
std::make_pair(*wxWHITE, (int) StateColor::Normal)));
|
|
m_btn_ok->SetMinSize(wxSize(FromDIP(55), FromDIP(24)));
|
|
m_btn_ok->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
|
EndModal(wxID_OK);
|
|
});
|
|
|
|
sizer->Add(m_btn_cancel, 0, wxRIGHT, FromDIP(12));
|
|
sizer->Add(m_btn_ok, 0);
|
|
|
|
return sizer;
|
|
}
|
|
|
|
void ColorDecomposeDialog::select_mode(DecomposeMode mode)
|
|
{
|
|
m_selected_mode = mode;
|
|
m_result = m_mode_results[mode_index(mode)];
|
|
update_card_styles();
|
|
update_matched_color_display();
|
|
update_ok_button_state();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_card_styles()
|
|
{
|
|
if (m_card_material_list) m_card_material_list->Refresh();
|
|
if (m_card_cmyw) m_card_cmyw->Refresh();
|
|
if (m_card_rybw) m_card_rybw->Refresh();
|
|
|
|
if (m_chk_material_list)
|
|
m_chk_material_list->SetValue(m_selected_mode == DecomposeMode::MaterialList);
|
|
if (m_chk_cmyw)
|
|
m_chk_cmyw->SetValue(m_selected_mode == DecomposeMode::CMYW);
|
|
if (m_chk_rybw)
|
|
m_chk_rybw->SetValue(m_selected_mode == DecomposeMode::RYBW);
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_card_visibility()
|
|
{
|
|
// Count physical filaments of the same type (excluding the source filament)
|
|
int same_type_count = 0;
|
|
for (size_t i = 0; i < m_filament_types.size(); ++i) {
|
|
if (static_cast<int>(i) == m_filament_idx)
|
|
continue;
|
|
if (material_type_matches(m_filament_types[i], m_preferred_type))
|
|
++same_type_count;
|
|
}
|
|
|
|
bool show_arb = (same_type_count >= 2);
|
|
bool show_cmyw = (m_preferred_type == kDecomposePlaBasicType);
|
|
bool show_rybw = (m_preferred_type == kDecomposePlaBasicType);
|
|
|
|
if (m_arb_column_panel) m_arb_column_panel->Show(show_arb);
|
|
if (m_card_material_list) m_card_material_list->Show(show_arb);
|
|
if (m_card_cmyw) m_card_cmyw->Show(show_cmyw);
|
|
if (m_card_rybw) m_card_rybw->Show(show_rybw);
|
|
|
|
bool any_visible = show_arb || show_cmyw || show_rybw;
|
|
if (m_no_card_hint)
|
|
m_no_card_hint->Show(!any_visible);
|
|
|
|
// Auto-select a visible mode when current selection becomes hidden
|
|
if (any_visible) {
|
|
bool cur_visible = false;
|
|
if (m_selected_mode == DecomposeMode::MaterialList && show_arb) cur_visible = true;
|
|
if (m_selected_mode == DecomposeMode::CMYW && show_cmyw) cur_visible = true;
|
|
if (m_selected_mode == DecomposeMode::RYBW && show_rybw) cur_visible = true;
|
|
if (!cur_visible) {
|
|
if (show_arb) select_mode(DecomposeMode::MaterialList);
|
|
else if (show_cmyw) select_mode(DecomposeMode::CMYW);
|
|
else select_mode(DecomposeMode::RYBW);
|
|
}
|
|
}
|
|
|
|
Layout();
|
|
update_ok_button_state();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_filament_limit_warning()
|
|
{
|
|
if (!m_limit_warning_panel || !m_limit_warning_text)
|
|
return;
|
|
|
|
size_t missing_new = 0;
|
|
if (m_missing_calculator) {
|
|
missing_new = m_missing_calculator(m_result);
|
|
} else {
|
|
const size_t source_physical_idx = m_filament_idx >= 0 ? static_cast<size_t>(m_filament_idx) : size_t(-1);
|
|
const std::vector<size_t>* indices =
|
|
m_physical_config_indices.empty() ? nullptr : &m_physical_config_indices;
|
|
missing_new = count_decompose_new_physical_filaments(
|
|
m_result, m_physical_colors, m_filament_types, source_physical_idx, indices);
|
|
}
|
|
// A result with fewer than 2 components (e.g. target color is already a
|
|
// standard base color shown as "100%") creates no mixed filament and no new
|
|
// physical filament, so it can never exceed the limit.
|
|
const bool creates_mixed = m_result.components.size() >= 2;
|
|
// +1 for the mixed filament slot that will be created after decomposition.
|
|
const size_t needed = m_current_filament_count + missing_new + 1;
|
|
const bool blocked = creates_mixed && needed > m_max_filament_count;
|
|
|
|
const bool was_shown = m_limit_warning_panel->IsShown();
|
|
|
|
if (!blocked) {
|
|
if (was_shown) {
|
|
m_limit_warning_panel->Hide();
|
|
Layout();
|
|
Fit();
|
|
CenterOnParent();
|
|
}
|
|
return;
|
|
}
|
|
|
|
wxString mode_name;
|
|
switch (m_selected_mode) {
|
|
case DecomposeMode::CMYW: mode_name = "CMYW"; break;
|
|
case DecomposeMode::RYBW: mode_name = "RYBW"; break;
|
|
case DecomposeMode::MaterialList: mode_name = _L("Material List"); break;
|
|
}
|
|
|
|
const wxString warning_text = format_wxstr(
|
|
_L("The material list supports at most %1% colors. After %2% decomposition, the material count would exceed %1%. Please delete unused filaments on the main screen before decomposing."),
|
|
m_max_filament_count, mode_name);
|
|
|
|
// Show first so the panel is laid out and the text control gets its real
|
|
// width, then wrap to that width so the paragraph fills the content area.
|
|
m_limit_warning_panel->Show();
|
|
Layout();
|
|
const int avail = m_limit_warning_text->GetClientSize().x;
|
|
m_limit_warning_text->SetLabel(warning_text);
|
|
if (avail > FromDIP(50))
|
|
m_limit_warning_text->Wrap(avail);
|
|
|
|
Layout();
|
|
// Only resize/recenter when the warning panel actually toggled from hidden
|
|
// to shown. While already visible, switching modes must not re-Fit/recenter
|
|
// the dialog, which would make it jump on every card switch.
|
|
if (!was_shown) {
|
|
Fit();
|
|
CenterOnParent();
|
|
}
|
|
}
|
|
|
|
void ColorDecomposeDialog::set_missing_physical_calculator(std::function<size_t(const ColorDecomposeResult&)> fn)
|
|
{
|
|
m_missing_calculator = std::move(fn);
|
|
update_ok_button_state();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_ok_button_state()
|
|
{
|
|
if (!m_btn_ok) return;
|
|
update_filament_limit_warning();
|
|
bool any_card_visible = (m_card_material_list && m_card_material_list->IsShown())
|
|
|| (m_card_cmyw && m_card_cmyw->IsShown())
|
|
|| (m_card_rybw && m_card_rybw->IsShown());
|
|
const bool blocked = m_limit_warning_panel && m_limit_warning_panel->IsShown();
|
|
m_btn_ok->Enable(any_card_visible && !blocked);
|
|
Layout();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_mode_card_content(DecomposeMode mode)
|
|
{
|
|
auto& controls = m_mode_cards[mode_index(mode)];
|
|
auto* sizer = controls.components_sizer;
|
|
auto* card = controls.card;
|
|
if (!sizer || !card)
|
|
return;
|
|
|
|
sizer->Clear(true);
|
|
const auto& components = m_mode_results[mode_index(mode)].components;
|
|
const size_t count = components.size();
|
|
if (count == 0) {
|
|
card->Layout();
|
|
card->Refresh();
|
|
return;
|
|
}
|
|
|
|
const int swatch_sz = FromDIP(24);
|
|
const int plus_gap = FromDIP(24);
|
|
const wxFont& ratio_font = Label::Body_13;
|
|
auto bind_select = [this, mode](wxWindow* w) {
|
|
w->Bind(wxEVT_LEFT_UP, [this, mode](wxMouseEvent&) {
|
|
select_mode(mode);
|
|
});
|
|
w->SetCursor(wxCursor(wxCURSOR_HAND));
|
|
};
|
|
|
|
for (size_t i = 0; i < count; ++i) {
|
|
auto* col = new wxBoxSizer(wxVERTICAL);
|
|
auto* swatch = create_color_swatch(card, components[i].colour, swatch_sz);
|
|
bind_select(swatch);
|
|
col->Add(swatch, 0, wxALIGN_CENTER_HORIZONTAL);
|
|
auto* ratio_text = new wxStaticText(card, wxID_ANY, wxString::Format("%d%%", components[i].ratio));
|
|
ratio_text->SetFont(ratio_font);
|
|
ratio_text->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
match_parent_bg(ratio_text, StateColor::darkModeColorFor(COLOR_BG_CARD));
|
|
bind_select(ratio_text);
|
|
col->Add(ratio_text, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(4));
|
|
sizer->Add(col, 0, wxALIGN_TOP);
|
|
|
|
if (i + 1 < count) {
|
|
sizer->AddStretchSpacer();
|
|
auto* plus_panel = new wxPanel(card, wxID_ANY, wxDefaultPosition, wxSize(plus_gap, swatch_sz));
|
|
plus_panel->SetMinSize(wxSize(plus_gap, swatch_sz));
|
|
plus_panel->SetMaxSize(wxSize(plus_gap, swatch_sz));
|
|
plus_panel->SetBackgroundColour(StateColor::darkModeColorFor(COLOR_BG_CARD));
|
|
auto* plus_sizer = new wxBoxSizer(wxVERTICAL);
|
|
auto* plus_label = new wxStaticText(plus_panel, wxID_ANY, "+");
|
|
plus_label->SetFont(Label::Body_13);
|
|
plus_label->SetForegroundColour(StateColor::darkModeColorFor(COLOR_TEXT_DARK));
|
|
match_parent_bg(plus_label, StateColor::darkModeColorFor(COLOR_BG_CARD));
|
|
bind_select(plus_panel);
|
|
bind_select(plus_label);
|
|
plus_sizer->AddStretchSpacer();
|
|
plus_sizer->Add(plus_label, 0, wxALIGN_CENTER_HORIZONTAL);
|
|
plus_sizer->AddStretchSpacer();
|
|
plus_panel->SetSizer(plus_sizer);
|
|
sizer->Add(plus_panel, 0, wxALIGN_TOP);
|
|
sizer->AddStretchSpacer();
|
|
}
|
|
}
|
|
|
|
const int card_width = FromDIP(128 + (count > 2 ? static_cast<int>(count - 2) * 31 : 0));
|
|
card->SetMinSize(wxSize(card_width, FromDIP(111)));
|
|
card->SetMaxSize(wxSize(card_width, FromDIP(111)));
|
|
|
|
card->Layout();
|
|
card->Refresh();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_mode_card_contents()
|
|
{
|
|
update_mode_card_content(DecomposeMode::MaterialList);
|
|
update_mode_card_content(DecomposeMode::CMYW);
|
|
update_mode_card_content(DecomposeMode::RYBW);
|
|
Layout();
|
|
Fit();
|
|
}
|
|
|
|
void ColorDecomposeDialog::update_matched_color_display()
|
|
{
|
|
if (!m_result.matched_color.IsOk())
|
|
m_result.matched_color = m_target_color;
|
|
|
|
if (m_matched_swatch) {
|
|
m_matched_swatch->SetBackgroundColour(m_result.matched_color);
|
|
m_matched_swatch->Refresh();
|
|
}
|
|
if (m_matched_rgb_text) {
|
|
m_matched_rgb_text->SetLabel(wxString::Format("RGB: %d, %d, %d",
|
|
m_result.matched_color.Red(), m_result.matched_color.Green(), m_result.matched_color.Blue()));
|
|
}
|
|
}
|
|
|
|
bool ColorDecomposeDialog::try_build_single_base_result(DecomposeMode mode, ColorDecomposeResult& out) const
|
|
{
|
|
// Gate by preferred type, matching card visibility: CMYW and RYBW only for PLA Basic.
|
|
if (mode == DecomposeMode::CMYW || mode == DecomposeMode::RYBW) {
|
|
if (m_preferred_type != kDecomposePlaBasicType)
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
static const DecomposeBaseColor cmyw_bases[] = {
|
|
DecomposeBaseColor::Cyan, DecomposeBaseColor::Magenta,
|
|
DecomposeBaseColor::Yellow, DecomposeBaseColor::White
|
|
};
|
|
static const DecomposeBaseColor rybw_bases[] = {
|
|
DecomposeBaseColor::Red, DecomposeBaseColor::Yellow,
|
|
DecomposeBaseColor::Blue, DecomposeBaseColor::White
|
|
};
|
|
const DecomposeBaseColor* bases = (mode == DecomposeMode::CMYW) ? cmyw_bases : rybw_bases;
|
|
const size_t base_count = (mode == DecomposeMode::CMYW)
|
|
? sizeof(cmyw_bases) / sizeof(cmyw_bases[0])
|
|
: sizeof(rybw_bases) / sizeof(rybw_bases[0]);
|
|
|
|
const std::string target_hex = decompose_normalize_color_hex(
|
|
m_target_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString());
|
|
|
|
for (size_t i = 0; i < base_count; ++i) {
|
|
const DecomposeBaseColor base = bases[i];
|
|
DecomposeOfficialComponent official =
|
|
lookup_decompose_official_component(m_preferred_type, base, pure_color_for_base(base));
|
|
if (decompose_normalize_color_hex(official.color_hex) != target_hex)
|
|
continue;
|
|
|
|
out = ColorDecomposeResult{};
|
|
out.mode = mode;
|
|
out.matched_color = hex_to_wx_colour(official.color_hex, m_target_color);
|
|
DecomposeComponent comp;
|
|
comp.colour = out.matched_color;
|
|
comp.ratio = 100;
|
|
comp.filament_index = -1;
|
|
comp.base_color = base;
|
|
out.components.push_back(comp);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ColorDecomposeDialog::compute_decomposition()
|
|
{
|
|
auto fallback_result = [this](DecomposeMode mode, const std::vector<DecomposeComponent>& components) {
|
|
ColorDecomposeResult result;
|
|
result.mode = mode;
|
|
result.components = components;
|
|
int total = 0;
|
|
double r = 0.0, g = 0.0, b = 0.0;
|
|
for (const auto& comp : result.components)
|
|
total += comp.ratio;
|
|
if (total <= 0)
|
|
total = 100;
|
|
for (const auto& comp : result.components) {
|
|
const double w = static_cast<double>(comp.ratio) / total;
|
|
r += comp.colour.Red() * w;
|
|
g += comp.colour.Green() * w;
|
|
b += comp.colour.Blue() * w;
|
|
}
|
|
result.matched_color = result.components.empty()
|
|
? m_target_color
|
|
: wxColour(static_cast<unsigned char>(std::clamp(r, 0.0, 255.0)),
|
|
static_cast<unsigned char>(std::clamp(g, 0.0, 255.0)),
|
|
static_cast<unsigned char>(std::clamp(b, 0.0, 255.0)));
|
|
return result;
|
|
};
|
|
|
|
std::vector<ColorDecomposePhysicalFilament> physical_filaments;
|
|
physical_filaments.reserve(m_physical_colors.size());
|
|
for (size_t i = 0; i < m_physical_colors.size(); ++i) {
|
|
if (m_filament_idx >= 0 && i == static_cast<size_t>(m_filament_idx))
|
|
continue;
|
|
ColorDecomposePhysicalFilament filament;
|
|
filament.color_hex = m_physical_colors[i];
|
|
filament.name = i < m_filament_names.size() ? m_filament_names[i] : "";
|
|
filament.type = i < m_filament_types.size() ? m_filament_types[i] : "";
|
|
filament.filament_index = static_cast<unsigned int>(i + 1);
|
|
physical_filaments.push_back(std::move(filament));
|
|
}
|
|
|
|
const ColorDecomposeRgb target_rgb = wx_colour_to_recipe_rgb(m_target_color);
|
|
|
|
auto material_recipe = recommend_from_physical_filaments(target_rgb, physical_filaments, m_preferred_type);
|
|
if (material_recipe.valid) {
|
|
m_mode_results[mode_index(DecomposeMode::MaterialList)] =
|
|
to_dialog_result(material_recipe, m_target_color);
|
|
} else {
|
|
std::vector<DecomposeComponent> components;
|
|
for (size_t i = 0; i < std::min<size_t>(2, physical_filaments.size()); ++i) {
|
|
DecomposeComponent comp;
|
|
comp.colour = wxColour(physical_filaments[i].color_hex);
|
|
comp.ratio = 50;
|
|
comp.filament_index = static_cast<int>(physical_filaments[i].filament_index);
|
|
components.push_back(comp);
|
|
}
|
|
if (components.empty()) {
|
|
components.push_back({m_target_color, 100, -1});
|
|
} else if (components.size() == 1) {
|
|
components.front().ratio = 100;
|
|
}
|
|
m_mode_results[mode_index(DecomposeMode::MaterialList)] =
|
|
fallback_result(DecomposeMode::MaterialList, components);
|
|
}
|
|
|
|
ColorDecomposeResult single_base;
|
|
if (try_build_single_base_result(DecomposeMode::CMYW, single_base)) {
|
|
m_mode_results[mode_index(DecomposeMode::CMYW)] = single_base;
|
|
} else {
|
|
auto cmyw_recipe = lookup_standard_recipe(target_rgb, ColorDecomposeRecipeMode::CMYW, m_preferred_type);
|
|
m_mode_results[mode_index(DecomposeMode::CMYW)] = cmyw_recipe.valid
|
|
? to_dialog_result(cmyw_recipe, m_target_color)
|
|
: fallback_result(DecomposeMode::CMYW, {
|
|
{CMYW_YELLOW, 50, -1, DecomposeBaseColor::Yellow},
|
|
{CMYW_CYAN, 50, -1, DecomposeBaseColor::Cyan}
|
|
});
|
|
}
|
|
|
|
if (try_build_single_base_result(DecomposeMode::RYBW, single_base)) {
|
|
m_mode_results[mode_index(DecomposeMode::RYBW)] = single_base;
|
|
} else {
|
|
auto rybw_recipe = lookup_standard_recipe(target_rgb, ColorDecomposeRecipeMode::RYBW, m_preferred_type);
|
|
m_mode_results[mode_index(DecomposeMode::RYBW)] = rybw_recipe.valid
|
|
? to_dialog_result(rybw_recipe, m_target_color)
|
|
: fallback_result(DecomposeMode::RYBW, {
|
|
{RYBW_YELLOW, 50, -1, DecomposeBaseColor::Yellow},
|
|
{RYBW_BLUE, 50, -1, DecomposeBaseColor::Blue}
|
|
});
|
|
}
|
|
|
|
m_result = m_mode_results[mode_index(m_selected_mode)];
|
|
update_mode_card_contents();
|
|
update_ok_button_state();
|
|
}
|
|
|
|
} // namespace GUI
|
|
} // namespace Slic3r
|