mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-24 18:37:39 +00:00
Blend mixed slots in the machine-send and AMS-sync thumbnails
This commit is contained in:
@@ -4,7 +4,10 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "EncodedFilament.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -265,4 +268,65 @@ wxBitmap create_filament_bitmap(const std::vector<wxColour>& colors, const wxSiz
|
||||
}
|
||||
}
|
||||
|
||||
void recompute_mixed_slot_colors(std::vector<wxColour>& colors,
|
||||
const Slic3r::DynamicPrintConfig& cfg)
|
||||
{
|
||||
const auto* is_mixed_opt = cfg.option<ConfigOptionBools>("filament_is_mixed");
|
||||
const auto* comp_opt = cfg.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
const auto* ratio_opt = cfg.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
||||
const auto* grad_opt = cfg.option<ConfigOptionBools>("filament_mixed_gradient");
|
||||
if (!is_mixed_opt || !comp_opt) return;
|
||||
|
||||
const size_t n = is_mixed_opt->values.size();
|
||||
if (colors.size() < n) colors.resize(n);
|
||||
|
||||
const auto* colour_opt = cfg.option<ConfigOptionStrings>("filament_colour");
|
||||
const auto kFallback = wxColour(128, 128, 128, 255);
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (!is_mixed_opt->values[i]) continue;
|
||||
|
||||
if (i >= comp_opt->values.size()) { colors[i] = kFallback; continue; }
|
||||
auto comp_ids = Slic3r::parse_mixed_components(comp_opt->values[i]);
|
||||
if (comp_ids.empty()) { colors[i] = kFallback; continue; }
|
||||
|
||||
bool is_gradient = grad_opt && i < grad_opt->values.size() && grad_opt->values[i];
|
||||
std::vector<unsigned int> use_ids = comp_ids;
|
||||
std::vector<int> weights;
|
||||
|
||||
if (is_gradient && comp_ids.size() >= 2) {
|
||||
use_ids = { comp_ids.front(), comp_ids.back() };
|
||||
weights = { 5000, 5000 };
|
||||
} else {
|
||||
auto ratios_d = Slic3r::parse_mixed_ratios(
|
||||
(ratio_opt && i < ratio_opt->values.size()) ? ratio_opt->values[i] : std::string{},
|
||||
comp_ids.size());
|
||||
weights.reserve(comp_ids.size());
|
||||
for (double r : ratios_d)
|
||||
weights.push_back(static_cast<int>(std::lround(r * 10000.0)));
|
||||
}
|
||||
|
||||
std::vector<std::string> hex_colors;
|
||||
hex_colors.reserve(use_ids.size());
|
||||
bool any_invalid = false;
|
||||
for (unsigned int id : use_ids) {
|
||||
if (id == 0 || id > colors.size()) { any_invalid = true; break; }
|
||||
wxColour c = colors[id - 1];
|
||||
if (c.IsOk() && (c.Red() > 0 || c.Green() > 0 || c.Blue() > 0)) {
|
||||
hex_colors.push_back(wxString::Format("#%02X%02X%02X", c.Red(), c.Green(), c.Blue()).ToStdString());
|
||||
} else if (colour_opt && (id - 1) < colour_opt->values.size()) {
|
||||
hex_colors.push_back(colour_opt->values[id - 1]);
|
||||
} else {
|
||||
any_invalid = true; break;
|
||||
}
|
||||
}
|
||||
if (any_invalid) { colors[i] = kFallback; continue; }
|
||||
|
||||
std::string hex = Slic3r::blend_color_multi(hex_colors, weights);
|
||||
wxColour blended(hex);
|
||||
if (!blended.IsOk()) blended = kFallback;
|
||||
colors[i] = wxColour(blended.Red(), blended.Green(), blended.Blue(), 255);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -7,6 +7,10 @@
|
||||
#include <wx/gdicmn.h>
|
||||
#include <vector>
|
||||
|
||||
// Orca: forward-declare so the header is self-contained outside libslic3r_gui's
|
||||
// force-included pch (the GUI test suite includes it directly).
|
||||
namespace Slic3r { class DynamicPrintConfig; }
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Fills a rect with a west->east linear gradient by drawing solid 1px columns.
|
||||
@@ -28,6 +32,12 @@ wxBitmap create_filament_bitmap(const std::vector<wxColour>& colors,
|
||||
const wxSize& size,
|
||||
bool force_gradient = false);
|
||||
|
||||
// Recompute blended representative colors for mixed (virtual) filament slots.
|
||||
// Reads mixed-filament config keys from cfg and writes back into colors[i]
|
||||
// for every slot where filament_is_mixed[i] is true.
|
||||
void recompute_mixed_slot_colors(std::vector<wxColour>& colors,
|
||||
const Slic3r::DynamicPrintConfig& cfg);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GUI_FilamentBitmapUtils_hpp_
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
@@ -5693,10 +5694,15 @@ void SelectMachineDialog::clone_thumbnail_data() {
|
||||
m_preview_colors_in_thumbnail.resize(m_materialList.size());
|
||||
}
|
||||
while (iter != m_materialList.end()) {
|
||||
int id = iter->first;
|
||||
Material * item = iter->second;
|
||||
MaterialItem *m = item->item;
|
||||
m_preview_colors_in_thumbnail[id] = m->m_material_coloul;
|
||||
// Orca: key the preview colours by filament slot, as m_cur_colors_in_thumbnail and
|
||||
// SyncAmsInfoDialog already do, so recompute_mixed_slot_colors() below can look a mixed
|
||||
// slot's component colours up by id (BBS keys this array by list position).
|
||||
if (item->id >= m_preview_colors_in_thumbnail.size()) {
|
||||
m_preview_colors_in_thumbnail.resize(item->id + 1);
|
||||
}
|
||||
m_preview_colors_in_thumbnail[item->id] = m->m_material_coloul;
|
||||
if (item->id < m_cur_colors_in_thumbnail.size()) {
|
||||
m_cur_colors_in_thumbnail[item->id] = m->m_ams_coloul;
|
||||
}
|
||||
@@ -5706,6 +5712,20 @@ void SelectMachineDialog::clone_thumbnail_data() {
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
|
||||
// Expand color arrays to cover mixed (virtual) slots and compute their blended colors
|
||||
const auto& cfg = wxGetApp().preset_bundle->project_config;
|
||||
size_t total = 0;
|
||||
if (auto* opt = cfg.option<ConfigOptionBools>("filament_is_mixed"))
|
||||
total = opt->values.size();
|
||||
size_t target = std::max(total, m_cur_colors_in_thumbnail.size());
|
||||
if (m_cur_colors_in_thumbnail.size() < target)
|
||||
m_cur_colors_in_thumbnail.resize(target);
|
||||
if (m_preview_colors_in_thumbnail.size() < target)
|
||||
m_preview_colors_in_thumbnail.resize(target);
|
||||
recompute_mixed_slot_colors(m_preview_colors_in_thumbnail, cfg);
|
||||
recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg);
|
||||
|
||||
//copy data
|
||||
auto &data = m_cur_input_thumbnail_data;
|
||||
m_preview_thumbnail_data.reset();
|
||||
@@ -5880,6 +5900,10 @@ void SelectMachineDialog::change_default_normal(int old_filament_id, wxColour te
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Recompute mixed slot colors after physical slot color change
|
||||
const auto& cfg = wxGetApp().preset_bundle->project_config;
|
||||
recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg);
|
||||
|
||||
ThumbnailData& data = m_cur_input_thumbnail_data;
|
||||
ThumbnailData& no_light_data = m_cur_no_light_thumbnail_data;
|
||||
if (data.width > 0 && data.height > 0 && data.width == no_light_data.width && data.height == no_light_data.height) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevMapping.h"
|
||||
#include "DeviceCore/DevStorage.h"
|
||||
#include "FilamentBitmapUtils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
@@ -2943,6 +2944,20 @@ void SyncAmsInfoDialog::clone_thumbnail_data()
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Expand color arrays to cover mixed (virtual) slots and compute their blended colors
|
||||
const auto& cfg = wxGetApp().preset_bundle->project_config;
|
||||
size_t total = 0;
|
||||
if (auto* opt = cfg.option<ConfigOptionBools>("filament_is_mixed"))
|
||||
total = opt->values.size();
|
||||
size_t target = std::max(total, m_cur_colors_in_thumbnail.size());
|
||||
if (m_cur_colors_in_thumbnail.size() < target)
|
||||
m_cur_colors_in_thumbnail.resize(target);
|
||||
if (m_preview_colors_in_thumbnail.size() < target)
|
||||
m_preview_colors_in_thumbnail.resize(target);
|
||||
recompute_mixed_slot_colors(m_preview_colors_in_thumbnail, cfg);
|
||||
recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg);
|
||||
|
||||
// copy data
|
||||
auto &data = m_cur_input_thumbnail_data;
|
||||
m_preview_thumbnail_data.reset();
|
||||
@@ -3131,6 +3146,10 @@ void SyncAmsInfoDialog::change_default_normal(int old_filament_id, wxColour temp
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Recompute mixed slot colors after physical slot color change
|
||||
const auto& cfg = wxGetApp().preset_bundle->project_config;
|
||||
recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg);
|
||||
|
||||
ThumbnailData &data = m_cur_input_thumbnail_data;
|
||||
ThumbnailData &no_light_data = m_cur_no_light_thumbnail_data;
|
||||
if (data.width > 0 && data.height > 0 && data.width == no_light_data.width && data.height == no_light_data.height) {
|
||||
|
||||
@@ -2,6 +2,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests_main.cpp
|
||||
test_dev_mapping.cpp
|
||||
test_filament_bitmap_utils.cpp
|
||||
test_network_versions.cpp
|
||||
test_action_source.cpp
|
||||
test_plugin_host_api.cpp
|
||||
|
||||
136
tests/slic3rutils/test_filament_bitmap_utils.cpp
Normal file
136
tests/slic3rutils/test_filament_bitmap_utils.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// recompute_mixed_slot_colors lives in libslic3r_gui; this is the only suite that links it.
|
||||
// Same Windows include prologue as test_dev_mapping.cpp (wx pulls in <windows.h>; keep
|
||||
// WIN32_LEAN_AND_MEAN / NOMINMAX ahead of the Catch2 headers).
|
||||
#ifdef WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <wx/colour.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/FilamentMixer.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/FilamentBitmapUtils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using Slic3r::GUI::recompute_mixed_slot_colors;
|
||||
|
||||
namespace {
|
||||
|
||||
// Two physical slots (1 = red, 2 = blue) and mixed slot 3 built from them.
|
||||
DynamicPrintConfig mixed_config(const std::string& components = "1,2", const std::string& ratios = "0.5,0.5")
|
||||
{
|
||||
DynamicPrintConfig cfg;
|
||||
cfg.set_key_value("filament_is_mixed", new ConfigOptionBools({false, false, true}));
|
||||
cfg.set_key_value("filament_mixed_components", new ConfigOptionStrings({"", "", components}));
|
||||
cfg.set_key_value("filament_mixed_sublayer_ratios", new ConfigOptionStrings({"", "", ratios}));
|
||||
cfg.set_key_value("filament_mixed_gradient", new ConfigOptionBools({false, false, false}));
|
||||
cfg.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#0000FF", "#000000"}));
|
||||
return cfg;
|
||||
}
|
||||
|
||||
wxColour expected_blend(const std::vector<std::string>& hex, const std::vector<int>& weights)
|
||||
{
|
||||
return wxColour(wxString(blend_color_multi(hex, weights)));
|
||||
}
|
||||
|
||||
// Compare channels one at a time so a failure names the channel.
|
||||
void require_same_rgb(const wxColour& actual, const wxColour& expected)
|
||||
{
|
||||
REQUIRE(int(actual.Red()) == int(expected.Red()));
|
||||
REQUIRE(int(actual.Green()) == int(expected.Green()));
|
||||
REQUIRE(int(actual.Blue()) == int(expected.Blue()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors blends a mixed slot from its components' colours", "[FilamentBitmapUtils]")
|
||||
{
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
recompute_mixed_slot_colors(colors, mixed_config());
|
||||
|
||||
REQUIRE(colors.size() == 3);
|
||||
require_same_rgb(colors[2], expected_blend({"#FF0000", "#0000FF"}, {5000, 5000}));
|
||||
REQUIRE(int(colors[2].Alpha()) == 255);
|
||||
// Physical slots are left alone.
|
||||
require_same_rgb(colors[0], wxColour(255, 0, 0));
|
||||
require_same_rgb(colors[1], wxColour(0, 0, 255));
|
||||
}
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors leaves the colours alone without mixed slots", "[FilamentBitmapUtils]")
|
||||
{
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
|
||||
SECTION("no mixed keys at all") {
|
||||
recompute_mixed_slot_colors(colors, DynamicPrintConfig{});
|
||||
}
|
||||
SECTION("mixed flags present but all false") {
|
||||
DynamicPrintConfig cfg;
|
||||
cfg.set_key_value("filament_is_mixed", new ConfigOptionBools({false, false}));
|
||||
cfg.set_key_value("filament_mixed_components", new ConfigOptionStrings({"", ""}));
|
||||
recompute_mixed_slot_colors(colors, cfg);
|
||||
}
|
||||
REQUIRE(colors.size() == 2);
|
||||
require_same_rgb(colors[0], wxColour(255, 0, 0));
|
||||
require_same_rgb(colors[1], wxColour(0, 0, 255));
|
||||
}
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors falls back to grey for a broken component reference", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const wxColour grey(128, 128, 128, 255);
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
|
||||
SECTION("dangling component id") {
|
||||
recompute_mixed_slot_colors(colors, mixed_config("1,9"));
|
||||
}
|
||||
SECTION("empty component list") {
|
||||
recompute_mixed_slot_colors(colors, mixed_config(""));
|
||||
}
|
||||
REQUIRE(colors.size() == 3);
|
||||
require_same_rgb(colors[2], grey);
|
||||
}
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors uses the project colour when a slot colour is unset", "[FilamentBitmapUtils]")
|
||||
{
|
||||
// Slot 2 carries no colour in the vector; filament_colour[1] = "#0000FF" is used instead.
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour()};
|
||||
recompute_mixed_slot_colors(colors, mixed_config());
|
||||
require_same_rgb(colors[2], expected_blend({"#FF0000", "#0000FF"}, {5000, 5000}));
|
||||
}
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors blends a gradient slot from its end points only", "[FilamentBitmapUtils]")
|
||||
{
|
||||
DynamicPrintConfig cfg;
|
||||
cfg.set_key_value("filament_is_mixed", new ConfigOptionBools({false, false, false, true}));
|
||||
cfg.set_key_value("filament_mixed_components", new ConfigOptionStrings({"", "", "", "1,2,3"}));
|
||||
cfg.set_key_value("filament_mixed_sublayer_ratios", new ConfigOptionStrings({"", "", "", "0.2,0.3,0.5"}));
|
||||
cfg.set_key_value("filament_mixed_gradient", new ConfigOptionBools({false, false, false, true}));
|
||||
cfg.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00", "#0000FF", "#000000"}));
|
||||
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour(0, 255, 0), wxColour(0, 0, 255)};
|
||||
recompute_mixed_slot_colors(colors, cfg);
|
||||
|
||||
REQUIRE(colors.size() == 4);
|
||||
require_same_rgb(colors[3], expected_blend({"#FF0000", "#0000FF"}, {5000, 5000}));
|
||||
}
|
||||
|
||||
TEST_CASE("recompute_mixed_slot_colors honours the configured ratios and is idempotent", "[FilamentBitmapUtils]")
|
||||
{
|
||||
std::vector<wxColour> colors{wxColour(255, 0, 0), wxColour(0, 0, 255)};
|
||||
const DynamicPrintConfig cfg = mixed_config("1,2", "0.7,0.3");
|
||||
recompute_mixed_slot_colors(colors, cfg);
|
||||
const wxColour first = colors[2];
|
||||
// The configured 70/30 ratio must reach the blend (it is not the equal-share default).
|
||||
require_same_rgb(first, expected_blend({"#FF0000", "#0000FF"}, {7000, 3000}));
|
||||
REQUIRE(first != expected_blend({"#FF0000", "#0000FF"}, {5000, 5000}));
|
||||
recompute_mixed_slot_colors(colors, cfg);
|
||||
require_same_rgb(colors[2], first);
|
||||
}
|
||||
Reference in New Issue
Block a user