mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-01 14:27:00 +00:00
Match mixed filament swatches to the editor's gradient preview
The sidebar Mixed Filament list, the extruder icons, the color painting gizmo and the canvas filament bar now show the same bottom-to-top fade the Edit Mixed Filament preview shows, custom gradient curves included, instead of a horizontal fade between the two component colours. Ordinary and vendor multi-colour filaments are drawn exactly as before.
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <wx/colour.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
@@ -134,3 +136,121 @@ TEST_CASE("recompute_mixed_slot_colors honours the configured ratios and is idem
|
||||
recompute_mixed_slot_colors(colors, cfg);
|
||||
require_same_rgb(colors[2], first);
|
||||
}
|
||||
|
||||
// --- mixed_gradient_ramp / sample_gradient_ramp -----------------------------------------
|
||||
//
|
||||
// The ramp is what every mixed filament swatch is drawn from, so these pin the three things
|
||||
// a plain two-endpoint fade got wrong: the reserved ratio band, the component order, and the
|
||||
// custom curve.
|
||||
|
||||
namespace {
|
||||
|
||||
// Slot 3 (index 2) is a gradient mix of physical slots 1 (red) and 2 (blue).
|
||||
DynamicPrintConfig gradient_config(const std::string& components = "1,2",
|
||||
const std::string& range = "0.9,0.1",
|
||||
const std::string& curve = "")
|
||||
{
|
||||
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_gradient", new ConfigOptionBools({false, false, true}));
|
||||
cfg.set_key_value("filament_mixed_gradient_range", new ConfigOptionStrings({"", "", range}));
|
||||
cfg.set_key_value("filament_mixed_gradient_curve", new ConfigOptionStrings({"", "", curve}));
|
||||
cfg.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#0000FF", "#000000"}));
|
||||
return cfg;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("mixed_gradient_ramp runs bottom to top and never reaches a pure component", "[FilamentBitmapUtils]")
|
||||
{
|
||||
// range "0.9,0.1": component 1 (red) is the majority at the bottom and the minority at the top.
|
||||
const auto ramp = Slic3r::GUI::mixed_gradient_ramp(gradient_config(), 2, 16);
|
||||
REQUIRE(ramp.size() == 16);
|
||||
|
||||
// Neither end is the pure component colour - the slicer clamps the blend to
|
||||
// [kGradientMinRatio, kGradientMaxRatio], which is exactly what a two-endpoint fade missed.
|
||||
REQUIRE(ramp.front() != wxColour(255, 0, 0));
|
||||
REQUIRE(ramp.back() != wxColour(0, 0, 255));
|
||||
|
||||
// Red falls and blue rises monotonically from bottom to top.
|
||||
for (size_t i = 1; i < ramp.size(); ++i) {
|
||||
REQUIRE(int(ramp[i].Red()) <= int(ramp[i - 1].Red()));
|
||||
REQUIRE(int(ramp[i].Blue()) >= int(ramp[i - 1].Blue()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("mixed_gradient_ramp follows the range's direction rather than the component order", "[FilamentBitmapUtils]")
|
||||
{
|
||||
const auto rising = Slic3r::GUI::mixed_gradient_ramp(gradient_config("1,2", "0.1,0.9"), 2, 16);
|
||||
const auto falling = Slic3r::GUI::mixed_gradient_ramp(gradient_config("1,2", "0.9,0.1"), 2, 16);
|
||||
REQUIRE(rising.size() == 16);
|
||||
REQUIRE(falling.size() == 16);
|
||||
|
||||
// "0.1,0.9" starts blue-heavy at the bottom; "0.9,0.1" starts red-heavy. Reversing the
|
||||
// range must reverse the ramp, which HSV-sorted endpoint colours could not express.
|
||||
REQUIRE(int(rising.front().Blue()) > int(rising.front().Red()));
|
||||
REQUIRE(int(falling.front().Red()) > int(falling.front().Blue()));
|
||||
require_same_rgb(rising.front(), falling.back());
|
||||
}
|
||||
|
||||
TEST_CASE("mixed_gradient_ramp bends with a custom curve", "[FilamentBitmapUtils]")
|
||||
{
|
||||
// Component 1 holds near its maximum for the first half, then drops - a shape a straight
|
||||
// fade between two endpoints cannot draw.
|
||||
const auto curved = Slic3r::GUI::mixed_gradient_ramp(
|
||||
gradient_config("1,2", "0.9,0.1", "0,0.9|0.5,0.85|1,0.1"), 2, 16);
|
||||
const auto linear = Slic3r::GUI::mixed_gradient_ramp(gradient_config("1,2", "0.9,0.1"), 2, 16);
|
||||
REQUIRE(curved.size() == 16);
|
||||
|
||||
// The curve holds component 1 high through the lower half, so every band up to mid height
|
||||
// is at least as red as the straight fade and mid height is strictly redder.
|
||||
for (size_t i = 0; i <= curved.size() / 2; ++i)
|
||||
REQUIRE(int(curved[i].Red()) >= int(linear[i].Red()));
|
||||
REQUIRE(int(curved[curved.size() / 2].Red()) > int(linear[linear.size() / 2].Red()));
|
||||
// It still ends blue-dominant, like the straight fade.
|
||||
REQUIRE(int(curved.back().Blue()) > int(curved.back().Red()));
|
||||
}
|
||||
|
||||
TEST_CASE("mixed_gradient_ramp is empty for anything but a two-component gradient slot", "[FilamentBitmapUtils]")
|
||||
{
|
||||
SECTION("slot is not mixed") {
|
||||
REQUIRE(Slic3r::GUI::mixed_gradient_ramp(gradient_config(), 0, 16).empty());
|
||||
}
|
||||
SECTION("gradient is off") {
|
||||
DynamicPrintConfig cfg = gradient_config();
|
||||
cfg.set_key_value("filament_mixed_gradient", new ConfigOptionBools({false, false, false}));
|
||||
REQUIRE(Slic3r::GUI::mixed_gradient_ramp(cfg, 2, 16).empty());
|
||||
}
|
||||
SECTION("three components") {
|
||||
REQUIRE(Slic3r::GUI::mixed_gradient_ramp(gradient_config("1,2,3"), 2, 16).empty());
|
||||
}
|
||||
SECTION("slot out of range") {
|
||||
REQUIRE(Slic3r::GUI::mixed_gradient_ramp(gradient_config(), 9, 16).empty());
|
||||
}
|
||||
SECTION("no mixed keys at all") {
|
||||
REQUIRE(Slic3r::GUI::mixed_gradient_ramp(DynamicPrintConfig{}, 0, 16).empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("sample_gradient_ramp blends each step through the shared blender", "[FilamentBitmapUtils]")
|
||||
{
|
||||
// A flat curve makes every step the same 30/70 mix, which must come out as the blend the
|
||||
// dialog's own swatches are drawn with - not a channel lerp between the two components.
|
||||
GradientCurve curve;
|
||||
curve.points = {{0.0, 0.3, NAN, NAN}, {1.0, 0.3, NAN, NAN}};
|
||||
const auto ramp = Slic3r::GUI::sample_gradient_ramp(wxColour(255, 0, 0), wxColour(0, 0, 255), curve, 4);
|
||||
REQUIRE(ramp.size() == 4);
|
||||
|
||||
const wxColour expected = Slic3r::GUI::blend_n_colors({wxColour(255, 0, 0), wxColour(0, 0, 255)}, {0.3, 0.7});
|
||||
for (const wxColour& c : ramp)
|
||||
require_same_rgb(c, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("sample_gradient_ramp returns nothing without a usable curve or step count", "[FilamentBitmapUtils]")
|
||||
{
|
||||
GradientCurve curve;
|
||||
REQUIRE(Slic3r::GUI::sample_gradient_ramp(wxColour(255, 0, 0), wxColour(0, 0, 255), curve, 8).empty());
|
||||
curve.points = {{0.0, kGradientMaxRatio, NAN, NAN}, {1.0, kGradientMinRatio, NAN, NAN}};
|
||||
REQUIRE(Slic3r::GUI::sample_gradient_ramp(wxColour(255, 0, 0), wxColour(0, 0, 255), curve, 0).empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user