mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-24 09:23:27 +00:00
Add mixed filament support: Implement MixedFilament and MixedFilamentManager classes for layer-based color mixing. Update CLI to handle downward_check option. Enhance GUI to display mixed filaments and integrate with existing filament management.
This commit is contained in:
+15
-1
@@ -1120,9 +1120,23 @@ int CLI::run(int argc, char **argv)
|
||||
sliced_info_t sliced_info;
|
||||
std::map<std::string, std::string> record_key_values;
|
||||
|
||||
// Only honor downward_check when explicitly requested on CLI.
|
||||
// This prevents accidental activation during normal GUI startup.
|
||||
bool downward_check_requested = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i] ? argv[i] : "";
|
||||
if (arg == "--downward_check" || boost::algorithm::starts_with(arg, "--downward_check=") ||
|
||||
arg == "--downward-check" || boost::algorithm::starts_with(arg, "--downward-check=")) {
|
||||
downward_check_requested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigOptionBool* downward_check_option = m_config.option<ConfigOptionBool>("downward_check");
|
||||
if (downward_check_option)
|
||||
if (downward_check_option && downward_check_requested)
|
||||
downward_check = downward_check_option->value;
|
||||
else
|
||||
downward_check = false;
|
||||
|
||||
bool start_gui = m_actions.empty() && !downward_check;
|
||||
if (start_gui) {
|
||||
|
||||
@@ -283,6 +283,8 @@ set(lisbslic3r_sources
|
||||
MinAreaBoundingBox.hpp
|
||||
MinimumSpanningTree.cpp
|
||||
MinimumSpanningTree.hpp
|
||||
MixedFilament.cpp
|
||||
MixedFilament.hpp
|
||||
miniz_extension.cpp
|
||||
miniz_extension.hpp
|
||||
ModelArrange.cpp
|
||||
|
||||
@@ -146,23 +146,34 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
|
||||
unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const
|
||||
{
|
||||
if (mixed_mgr && mixed_mgr->is_mixed(filament_id, num_physical))
|
||||
return mixed_mgr->resolve(filament_id, num_physical, this->layer_index);
|
||||
return filament_id;
|
||||
}
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().wall_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override) - 1;
|
||||
unsigned int id = (this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override;
|
||||
return resolve_mixed_1based(id) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().sparse_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override) - 1;
|
||||
unsigned int id = (this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override;
|
||||
return resolve_mixed_1based(id) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().solid_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override) - 1;
|
||||
unsigned int id = (this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override;
|
||||
return resolve_mixed_1based(id) - 1;
|
||||
}
|
||||
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
@@ -184,6 +195,7 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c
|
||||
} else
|
||||
extruder = this->extruder_override;
|
||||
|
||||
extruder = resolve_mixed_1based(extruder);
|
||||
return (extruder == 0) ? 0 : extruder - 1;
|
||||
}
|
||||
|
||||
@@ -208,6 +220,9 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
|
||||
m_is_BBL_printer = object.print()->is_BBL_printer();
|
||||
m_print_full_config = &object.print()->full_print_config();
|
||||
m_print_object_ptr = &object;
|
||||
// Mixed filament support.
|
||||
m_mixed_mgr = &object.print()->mixed_filament_manager();
|
||||
m_num_physical = object.print()->config().filament_diameter.size();
|
||||
if (object.layers().empty())
|
||||
return;
|
||||
|
||||
@@ -271,6 +286,9 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
||||
m_is_BBL_printer = print.is_BBL_printer();
|
||||
m_print_full_config = &print.full_print_config();
|
||||
m_print_config_ptr = &print.config();
|
||||
// Mixed filament support.
|
||||
m_mixed_mgr = &print.mixed_filament_manager();
|
||||
m_num_physical = print.config().filament_diameter.size();
|
||||
|
||||
// Initialize the print layers for all objects and all layers.
|
||||
coordf_t object_bottom_z = 0.;
|
||||
@@ -480,8 +498,8 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
ExtrusionRole role = support_layer->support_fills.role();
|
||||
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
|
||||
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
||||
unsigned int extruder_support = object.config().support_filament.value;
|
||||
unsigned int extruder_interface = object.config().support_interface_filament.value;
|
||||
unsigned int extruder_support = resolve_mixed(object.config().support_filament.value, layer_tools.layer_index);
|
||||
unsigned int extruder_interface = resolve_mixed(object.config().support_interface_filament.value, layer_tools.layer_index);
|
||||
if (has_support)
|
||||
layer_tools.extruders.push_back(extruder_support);
|
||||
if (has_interface)
|
||||
@@ -505,6 +523,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
// Collect the object extruders.
|
||||
for (auto layer : object.layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
|
||||
// Store the sequential layer index and mixed-filament context for resolution.
|
||||
layer_tools.layer_index = layerCount;
|
||||
layer_tools.mixed_mgr = m_mixed_mgr;
|
||||
layer_tools.num_physical = m_num_physical;
|
||||
|
||||
// Override extruder with the next
|
||||
for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
|
||||
@@ -528,9 +550,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
}
|
||||
|
||||
if (something_nonoverriddable){
|
||||
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
unsigned int wall_ext = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override;
|
||||
wall_ext = resolve_mixed(wall_ext, layerCount);
|
||||
layer_tools.extruders.emplace_back(wall_ext);
|
||||
if (layerCount == 0) {
|
||||
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
firstLayerExtruders.emplace_back(wall_ext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,11 +582,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
if (something_nonoverriddable || !m_print_config_ptr) {
|
||||
if (extruder_override == 0) {
|
||||
if (has_solid_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().solid_infill_filament);
|
||||
layer_tools.extruders.emplace_back(resolve_mixed(region.config().solid_infill_filament, layerCount));
|
||||
if (has_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament);
|
||||
layer_tools.extruders.emplace_back(resolve_mixed(region.config().sparse_infill_filament, layerCount));
|
||||
} else if (has_solid_infill || has_infill)
|
||||
layer_tools.extruders.emplace_back(extruder_override);
|
||||
layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount));
|
||||
}
|
||||
if (has_solid_infill || has_infill)
|
||||
layer_tools.has_object = true;
|
||||
@@ -1414,5 +1438,12 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Resolve a 1-based filament ID through the mixed-filament manager.
|
||||
unsigned int ToolOrdering::resolve_mixed(unsigned int filament_id_1based, int layer_index) const
|
||||
{
|
||||
if (m_mixed_mgr && m_mixed_mgr->is_mixed(filament_id_1based, m_num_physical))
|
||||
return m_mixed_mgr->resolve(filament_id_1based, m_num_physical, layer_index);
|
||||
return filament_id_1based;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define slic3r_ToolOrdering_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../MixedFilament.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -119,6 +120,8 @@ public:
|
||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||
// If not overriden, it is set to 0.
|
||||
unsigned int extruder_override = 0;
|
||||
// Sequential layer index (0-based), used by mixed-filament resolution.
|
||||
int layer_index = 0;
|
||||
// Should a skirt be printed at this layer?
|
||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||
bool has_skirt = false;
|
||||
@@ -138,7 +141,13 @@ public:
|
||||
return m_wiping_extrusions;
|
||||
}
|
||||
|
||||
// Mixed-filament resolution context (set by ToolOrdering during collect_extruders).
|
||||
const MixedFilamentManager *mixed_mgr = nullptr;
|
||||
size_t num_physical = 0;
|
||||
|
||||
private:
|
||||
// Resolve a 1-based filament ID through the mixed-filament manager for this layer.
|
||||
unsigned int resolve_mixed_1based(unsigned int filament_id) const;
|
||||
// This object holds list of extrusion that will be used for extruder wiping
|
||||
WipingExtrusions m_wiping_extrusions;
|
||||
};
|
||||
@@ -203,6 +212,11 @@ private:
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const PrintObject& object);
|
||||
|
||||
// Resolve a 1-based filament ID through the mixed-filament manager.
|
||||
// Returns the resolved physical extruder (1-based). If the ID is not a
|
||||
// mixed filament or no manager is set, returns the input unchanged.
|
||||
unsigned int resolve_mixed(unsigned int filament_id_1based, int layer_index) const;
|
||||
|
||||
std::vector<LayerTools> m_layer_tools;
|
||||
// First printing extruder, including the multi-material priming sequence.
|
||||
unsigned int m_first_printing_extruder = (unsigned int)-1;
|
||||
@@ -215,6 +229,10 @@ private:
|
||||
const PrintConfig* m_print_config_ptr = nullptr;
|
||||
const PrintObject* m_print_object_ptr = nullptr;
|
||||
bool m_is_BBL_printer = false;
|
||||
// Mixed filament support: pointer to manager (owned by Print) and
|
||||
// number of physical extruders.
|
||||
const MixedFilamentManager* m_mixed_mgr = nullptr;
|
||||
size_t m_num_physical = 0;
|
||||
};
|
||||
|
||||
} // namespace SLic3r
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "MixedFilament.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Colour helpers (internal)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct RGB {
|
||||
int r = 0, g = 0, b = 0;
|
||||
};
|
||||
|
||||
// Parse "#RRGGBB" to RGB. Returns black on failure.
|
||||
static RGB parse_hex_color(const std::string &hex)
|
||||
{
|
||||
RGB c;
|
||||
if (hex.size() >= 7 && hex[0] == '#') {
|
||||
c.r = std::stoi(hex.substr(1, 2), nullptr, 16);
|
||||
c.g = std::stoi(hex.substr(3, 2), nullptr, 16);
|
||||
c.b = std::stoi(hex.substr(5, 2), nullptr, 16);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static std::string rgb_to_hex(const RGB &c)
|
||||
{
|
||||
char buf[8];
|
||||
std::snprintf(buf, sizeof(buf), "#%02X%02X%02X", c.r, c.g, c.b);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MixedFilamentManager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void MixedFilamentManager::auto_generate(const std::vector<std::string> &filament_colours)
|
||||
{
|
||||
// Keep a copy of the old list so we can preserve user-modified ratios and
|
||||
// enabled flags.
|
||||
std::vector<MixedFilament> old = std::move(m_mixed);
|
||||
m_mixed.clear();
|
||||
|
||||
const size_t n = filament_colours.size();
|
||||
if (n < 2)
|
||||
return;
|
||||
|
||||
// Generate all C(N,2) pairwise combinations.
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
for (size_t j = i + 1; j < n; ++j) {
|
||||
MixedFilament mf;
|
||||
mf.component_a = static_cast<unsigned int>(i + 1); // 1-based
|
||||
mf.component_b = static_cast<unsigned int>(j + 1);
|
||||
mf.ratio_a = 1;
|
||||
mf.ratio_b = 1;
|
||||
mf.enabled = true;
|
||||
|
||||
// Try to preserve previous settings.
|
||||
for (const auto &prev : old) {
|
||||
if (prev.component_a == mf.component_a &&
|
||||
prev.component_b == mf.component_b) {
|
||||
mf.ratio_a = prev.ratio_a;
|
||||
mf.ratio_b = prev.ratio_b;
|
||||
mf.enabled = prev.enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mf.display_color = blend_color(filament_colours[i],
|
||||
filament_colours[j],
|
||||
mf.ratio_a, mf.ratio_b);
|
||||
m_mixed.push_back(mf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int MixedFilamentManager::resolve(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index) const
|
||||
{
|
||||
if (!is_mixed(filament_id, num_physical))
|
||||
return filament_id;
|
||||
|
||||
const size_t idx = index_of(filament_id, num_physical);
|
||||
if (idx >= m_mixed.size())
|
||||
return 1; // fallback to first extruder
|
||||
|
||||
const MixedFilament &mf = m_mixed[idx];
|
||||
const int cycle = mf.ratio_a + mf.ratio_b;
|
||||
if (cycle <= 0)
|
||||
return mf.component_a;
|
||||
|
||||
const int pos = ((layer_index % cycle) + cycle) % cycle; // safe modulo for negatives
|
||||
return (pos < mf.ratio_a) ? mf.component_a : mf.component_b;
|
||||
}
|
||||
|
||||
std::string MixedFilamentManager::blend_color(const std::string &color_a,
|
||||
const std::string &color_b,
|
||||
int ratio_a, int ratio_b)
|
||||
{
|
||||
RGB a = parse_hex_color(color_a);
|
||||
RGB b = parse_hex_color(color_b);
|
||||
|
||||
// Additive blend: min(a + b, 255) per channel.
|
||||
// For unequal ratios, weight accordingly.
|
||||
const float total = static_cast<float>(ratio_a + ratio_b);
|
||||
const float wa = (total > 0.f) ? static_cast<float>(ratio_a) / total : 0.5f;
|
||||
const float wb = 1.f - wa;
|
||||
|
||||
// Use screen blending which is additive-like without oversaturation:
|
||||
// screen(A, B) = A + B - A*B/255
|
||||
// Weighted variant: blend each channel independently.
|
||||
auto screen_ch = [](int ca, int cb, float wa, float wb) -> int {
|
||||
// Weighted additive with clamping – matches user expectation:
|
||||
// Red(255,0,0) + Green(0,255,0) = Yellow(255,255,0)
|
||||
float v = static_cast<float>(ca) * wa + static_cast<float>(cb) * wb;
|
||||
// Boost towards additive: add the minimum so pure colours combine fully.
|
||||
float additive = std::min(static_cast<float>(ca + cb), 255.f);
|
||||
// Blend between weighted-average and full-additive based on colour distance.
|
||||
float result = wa * static_cast<float>(ca) + wb * static_cast<float>(cb);
|
||||
// For the 1:1 case, use pure additive (clamped) to get R+G=Y.
|
||||
if (std::abs(wa - wb) < 0.01f)
|
||||
result = additive;
|
||||
return std::min(static_cast<int>(std::round(result)), 255);
|
||||
};
|
||||
|
||||
RGB out;
|
||||
out.r = screen_ch(a.r, b.r, wa, wb);
|
||||
out.g = screen_ch(a.g, b.g, wa, wb);
|
||||
out.b = screen_ch(a.b, b.b, wa, wb);
|
||||
|
||||
return rgb_to_hex(out);
|
||||
}
|
||||
|
||||
size_t MixedFilamentManager::enabled_count() const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (const auto &mf : m_mixed)
|
||||
if (mf.enabled)
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
std::vector<std::string> MixedFilamentManager::display_colors() const
|
||||
{
|
||||
std::vector<std::string> colors;
|
||||
for (const auto &mf : m_mixed)
|
||||
if (mf.enabled)
|
||||
colors.push_back(mf.display_color);
|
||||
return colors;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,108 @@
|
||||
#ifndef slic3r_MixedFilament_hpp_
|
||||
#define slic3r_MixedFilament_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Represents a virtual "mixed" filament created by alternating layers of two
|
||||
// physical filaments. The display colour is an additive RGB blend so that,
|
||||
// for example, Red + Green previews as Yellow.
|
||||
struct MixedFilament
|
||||
{
|
||||
// 1-based physical filament IDs that are combined.
|
||||
unsigned int component_a = 1;
|
||||
unsigned int component_b = 2;
|
||||
|
||||
// Layer-alternation ratio. With ratio_a = 2, ratio_b = 1 the cycle is
|
||||
// A, A, B, A, A, B, ...
|
||||
int ratio_a = 1;
|
||||
int ratio_b = 1;
|
||||
|
||||
// Whether this mixed filament is enabled (available for assignment).
|
||||
bool enabled = true;
|
||||
|
||||
// Computed display colour as "#RRGGBB".
|
||||
std::string display_color;
|
||||
|
||||
bool operator==(const MixedFilament &rhs) const
|
||||
{
|
||||
return component_a == rhs.component_a &&
|
||||
component_b == rhs.component_b &&
|
||||
ratio_a == rhs.ratio_a &&
|
||||
ratio_b == rhs.ratio_b &&
|
||||
enabled == rhs.enabled;
|
||||
}
|
||||
bool operator!=(const MixedFilament &rhs) const { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MixedFilamentManager
|
||||
//
|
||||
// Owns the list of mixed filaments and provides helpers used by the slicing
|
||||
// pipeline to resolve virtual IDs back to physical extruders.
|
||||
//
|
||||
// Virtual filament IDs are numbered starting at (num_physical + 1). For a
|
||||
// 4-extruder printer the first mixed filament has ID 5, the second 6, etc.
|
||||
// ---------------------------------------------------------------------------
|
||||
class MixedFilamentManager
|
||||
{
|
||||
public:
|
||||
MixedFilamentManager() = default;
|
||||
|
||||
// ---- Auto-generation ------------------------------------------------
|
||||
|
||||
// Rebuild the mixed-filament list from the current set of physical
|
||||
// filament colours. Generates all C(N,2) pairwise combinations.
|
||||
// Previous ratio/enabled state is preserved when a combination still
|
||||
// exists.
|
||||
void auto_generate(const std::vector<std::string> &filament_colours);
|
||||
|
||||
// ---- Queries --------------------------------------------------------
|
||||
|
||||
// True when `filament_id` (1-based) refers to a mixed filament.
|
||||
bool is_mixed(unsigned int filament_id, size_t num_physical) const
|
||||
{
|
||||
return filament_id > num_physical && index_of(filament_id, num_physical) < m_mixed.size();
|
||||
}
|
||||
|
||||
// Resolve a mixed filament ID to a physical extruder (1-based) for the
|
||||
// given `layer_index`. Returns `filament_id` unchanged when it is not a
|
||||
// mixed filament.
|
||||
unsigned int resolve(unsigned int filament_id, size_t num_physical, int layer_index) const;
|
||||
|
||||
// Compute a display colour by additively blending the two component
|
||||
// colours. `filament_colours` contains the physical colours only.
|
||||
static std::string blend_color(const std::string &color_a,
|
||||
const std::string &color_b,
|
||||
int ratio_a, int ratio_b);
|
||||
|
||||
// ---- Accessors ------------------------------------------------------
|
||||
|
||||
const std::vector<MixedFilament> &mixed_filaments() const { return m_mixed; }
|
||||
std::vector<MixedFilament> &mixed_filaments() { return m_mixed; }
|
||||
|
||||
size_t enabled_count() const;
|
||||
|
||||
// Total filament count = num_physical + number of *enabled* mixed filaments.
|
||||
size_t total_filaments(size_t num_physical) const { return num_physical + enabled_count(); }
|
||||
|
||||
// Return the display colours of all enabled mixed filaments (in order).
|
||||
std::vector<std::string> display_colors() const;
|
||||
|
||||
private:
|
||||
// Convert a 1-based virtual ID to a 0-based index into m_mixed.
|
||||
size_t index_of(unsigned int filament_id, size_t num_physical) const
|
||||
{
|
||||
return static_cast<size_t>(filament_id - num_physical - 1);
|
||||
}
|
||||
|
||||
std::vector<MixedFilament> m_mixed;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_MixedFilament_hpp_ */
|
||||
@@ -2477,6 +2477,17 @@ void ModelVolume::update_extruder_count(size_t extruder_count)
|
||||
std::vector<int> used_extruders = get_extruders();
|
||||
for (int extruder_id : used_extruders) {
|
||||
if (extruder_id > extruder_count) {
|
||||
std::string used_extruders_str;
|
||||
for (size_t i = 0; i < used_extruders.size(); ++i) {
|
||||
if (i > 0)
|
||||
used_extruders_str += ",";
|
||||
used_extruders_str += std::to_string(used_extruders[i]);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "ModelVolume::update_extruder_count clipping painted extruders to limit"
|
||||
<< " volume_name=" << this->name
|
||||
<< " volume_id=" << this->id().id
|
||||
<< " requested_limit=" << extruder_count
|
||||
<< " used_extruders=[" << used_extruders_str << "]";
|
||||
mmu_segmentation_facets.set_enforcer_block_type_limit(*this, (EnforcerBlockerType)extruder_count);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -2195,11 +2196,37 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
|
||||
|
||||
// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
|
||||
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback) {
|
||||
const size_t num_facets_states = print_object.print()->config().filament_colour.size() + 1;
|
||||
const size_t num_physical_filaments = print_object.print()->config().filament_colour.size();
|
||||
const size_t num_total_filaments = print_object.print()->mixed_filament_manager().total_filaments(num_physical_filaments);
|
||||
const size_t num_facets_states = num_total_filaments + 1;
|
||||
const float max_width = float(print_object.config().mmu_segmented_region_max_width.value);
|
||||
const float interlocking_depth = float(print_object.config().mmu_segmented_region_interlocking_depth.value);
|
||||
const bool interlocking_beam = print_object.config().interlocking_beam.value;
|
||||
|
||||
size_t max_painted_state = 0;
|
||||
for (const ModelVolume *mv : print_object.model_object()->volumes) {
|
||||
if (!mv->is_model_part())
|
||||
continue;
|
||||
const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states;
|
||||
for (size_t state_idx = static_cast<size_t>(EnforcerBlockerType::Extruder1); state_idx < used_states.size(); ++state_idx) {
|
||||
if (used_states[state_idx])
|
||||
max_painted_state = std::max(max_painted_state, state_idx);
|
||||
}
|
||||
}
|
||||
if (max_painted_state >= num_facets_states) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "multi_material_segmentation_by_painting dropping painted states above segmentation range"
|
||||
<< " max_painted_state=" << max_painted_state
|
||||
<< " num_facets_states=" << num_facets_states
|
||||
<< " physical_filaments=" << num_physical_filaments
|
||||
<< " total_filaments=" << num_total_filaments;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(debug) << "multi_material_segmentation_by_painting state range check"
|
||||
<< " max_painted_state=" << max_painted_state
|
||||
<< " num_facets_states=" << num_facets_states
|
||||
<< " physical_filaments=" << num_physical_filaments
|
||||
<< " total_filaments=" << num_total_filaments;
|
||||
}
|
||||
|
||||
const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
|
||||
return {mv.mmu_segmentation_facets, mv.is_mm_painted(), false};
|
||||
};
|
||||
|
||||
@@ -1811,7 +1811,9 @@ void PresetBundle::export_selections(AppConfig &config)
|
||||
config.set_printer_setting(printer_name, name, filament_presets[i]);
|
||||
}
|
||||
CNumericLocalesSetter locales_setter;
|
||||
std::string filament_colors = boost::algorithm::join(project_config.option<ConfigOptionStrings>("filament_colour")->values, ",");
|
||||
std::vector<std::string> physical_filament_colors = project_config.option<ConfigOptionStrings>("filament_colour")->values;
|
||||
physical_filament_colors.resize(filament_presets.size(), "#26A69A");
|
||||
std::string filament_colors = boost::algorithm::join(physical_filament_colors, ",");
|
||||
config.set_printer_setting(printer_name, "filament_colors", filament_colors);
|
||||
std::string flush_volumes_matrix = boost::algorithm::join(project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values |
|
||||
boost::adaptors::transformed(static_cast<std::string (*)(double)>(std::to_string)),
|
||||
@@ -3256,6 +3258,16 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
|
||||
}
|
||||
this->project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values = new_matrix;
|
||||
}
|
||||
|
||||
// Keep project colours aligned to physical filaments, then regenerate mixed
|
||||
// (virtual) entries from the physical set only.
|
||||
{
|
||||
ConfigOptionStrings *color_opt = this->project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
if (color_opt) {
|
||||
color_opt->values.resize(num_filaments, "#26A69A");
|
||||
this->mixed_filaments.auto_generate(color_opt->values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Preset.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "MixedFilament.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
@@ -153,6 +154,9 @@ public:
|
||||
std::map<int, DynamicPrintConfig> filament_ams_list;
|
||||
std::vector<std::vector<std::string>> ams_multi_color_filment;
|
||||
|
||||
// Mixed (virtual) filaments for layer-based colour mixing.
|
||||
MixedFilamentManager mixed_filaments;
|
||||
|
||||
// Snapmaker
|
||||
std::map<int, std::pair<std::string, std::string>> machine_filaments;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
#include "MixedFilament.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
@@ -896,6 +897,8 @@ public:
|
||||
const PrintConfig& config() const { return m_config; }
|
||||
const PrintObjectConfig& default_object_config() const { return m_default_object_config; }
|
||||
const PrintRegionConfig& default_region_config() const { return m_default_region_config; }
|
||||
const MixedFilamentManager& mixed_filament_manager() const { return m_mixed_filament_mgr; }
|
||||
MixedFilamentManager& mixed_filament_manager() { return m_mixed_filament_mgr; }
|
||||
ConstPrintObjectPtrsAdaptor objects() const { return ConstPrintObjectPtrsAdaptor(&m_objects); }
|
||||
PrintObject* get_object(size_t idx) { return const_cast<PrintObject*>(m_objects[idx]); }
|
||||
const PrintObject* get_object(size_t idx) const { return m_objects[idx]; }
|
||||
@@ -1028,6 +1031,7 @@ private:
|
||||
PrintConfig m_config;
|
||||
PrintObjectConfig m_default_object_config;
|
||||
PrintRegionConfig m_default_region_config;
|
||||
MixedFilamentManager m_mixed_filament_mgr;
|
||||
PrintObjectPtrs m_objects;
|
||||
PrintRegionPtrs m_print_regions;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Print.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -1188,6 +1189,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
}
|
||||
}
|
||||
|
||||
// Regenerate mixed (virtual) filaments from physical filament colours only.
|
||||
std::vector<std::string> physical_filament_colors = m_config.filament_colour.values;
|
||||
physical_filament_colors.resize(num_extruders, "#26A69A");
|
||||
m_mixed_filament_mgr.auto_generate(physical_filament_colors);
|
||||
// Total filaments = physical extruders + enabled mixed (virtual) filaments.
|
||||
// Used for extruder ID clamping so that virtual IDs are accepted.
|
||||
size_t num_total_filaments = m_mixed_filament_mgr.total_filaments(num_extruders);
|
||||
|
||||
ModelObjectStatusDB model_object_status_db;
|
||||
|
||||
// 1) Synchronize model objects.
|
||||
@@ -1375,7 +1384,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if (object_config_changed)
|
||||
model_object.config.assign_config(model_object_new.config);
|
||||
if (! object_diff.empty() || object_config_changed || num_extruders_changed ) {
|
||||
PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders );
|
||||
PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_total_filaments );
|
||||
for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) {
|
||||
t_config_option_keys diff = print_object_status.print_object->config().diff(new_config);
|
||||
if (! diff.empty()) {
|
||||
@@ -1438,10 +1447,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
||||
// Producing the config for PrintObject on demand, caching it at print_object_last.
|
||||
const PrintObject *print_object_last = nullptr;
|
||||
auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders ](PrintObject *print_object) {
|
||||
auto print_object_apply_config = [this, &print_object_last, model_object, num_total_filaments ](PrintObject *print_object) {
|
||||
print_object->config_apply(print_object_last ?
|
||||
print_object_last->config() :
|
||||
PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders ));
|
||||
PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_total_filaments ));
|
||||
print_object_last = print_object;
|
||||
};
|
||||
if (old.empty()) {
|
||||
@@ -1581,9 +1590,43 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
used_facet_states[state_idx] |= volume_used_facet_states[state_idx];
|
||||
}
|
||||
|
||||
size_t dropped_painted_states = 0;
|
||||
for (size_t state_idx = static_cast<size_t>(EnforcerBlockerType::Extruder1); state_idx < used_facet_states.size(); ++state_idx) {
|
||||
if (used_facet_states[state_idx])
|
||||
painting_extruders.emplace_back(state_idx);
|
||||
if (!used_facet_states[state_idx])
|
||||
continue;
|
||||
if (state_idx <= num_total_filaments)
|
||||
painting_extruders.emplace_back(static_cast<unsigned int>(state_idx));
|
||||
else
|
||||
++dropped_painted_states;
|
||||
}
|
||||
|
||||
if (dropped_painted_states > 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Print::apply dropping painted extruder IDs above available filament range"
|
||||
<< " dropped_states=" << dropped_painted_states
|
||||
<< " physical_filaments=" << num_extruders
|
||||
<< " total_filaments=" << num_total_filaments;
|
||||
}
|
||||
|
||||
if (!painting_extruders.empty()) {
|
||||
std::string painting_ids;
|
||||
for (size_t i = 0; i < painting_extruders.size(); ++i) {
|
||||
if (i > 0)
|
||||
painting_ids += ",";
|
||||
painting_ids += std::to_string(painting_extruders[i]);
|
||||
}
|
||||
|
||||
const unsigned int max_painted_extruder = *std::max_element(painting_extruders.begin(), painting_extruders.end());
|
||||
if (max_painted_extruder > num_total_filaments) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Print::apply detected painted extruder IDs above available filament range"
|
||||
<< " painted_extruders=[" << painting_ids << "]"
|
||||
<< " physical_filaments=" << num_extruders
|
||||
<< " total_filaments=" << num_total_filaments;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Print::apply collected painted extruders"
|
||||
<< " painted_extruders=[" << painting_ids << "]"
|
||||
<< " physical_filaments=" << num_extruders
|
||||
<< " total_filaments=" << num_total_filaments;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) {
|
||||
@@ -1602,7 +1645,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
verify_update_print_object_regions(
|
||||
print_object.model_object()->volumes,
|
||||
m_default_region_config,
|
||||
num_extruders,
|
||||
num_total_filaments,
|
||||
*print_object_regions,
|
||||
[it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
|
||||
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||
@@ -1627,7 +1670,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
LayerRanges(print_object.model_object()->layer_config_ranges),
|
||||
m_default_region_config,
|
||||
model_object_status.print_instances.front().trafo,
|
||||
num_extruders ,
|
||||
num_total_filaments ,
|
||||
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value),
|
||||
painting_extruders,
|
||||
print_object.is_fuzzy_skin_painted());
|
||||
|
||||
@@ -3157,9 +3157,11 @@ void PrintObject::bridge_over_infill()
|
||||
|
||||
} // void PrintObject::bridge_over_infill()
|
||||
|
||||
static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders)
|
||||
// Clamp extruder IDs that exceed the total number of available filaments
|
||||
// (physical + virtual/mixed) back to the default extruder.
|
||||
static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_total_filaments)
|
||||
{
|
||||
if (opt.value > (int)num_extruders)
|
||||
if (opt.value > (int)num_total_filaments)
|
||||
// assign the default extruder
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
@@ -854,8 +854,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
||||
double z = print_object.get_layer(int(range.begin()))->slice_z;
|
||||
auto it_layer_range = layer_range_first(layer_ranges, z);
|
||||
// BBS
|
||||
const size_t num_extruders = print_object.print()->config().filament_diameter.size();
|
||||
// MM segmentation channels correspond to filament IDs (1-based), which now
|
||||
// include enabled mixed / virtual filaments.
|
||||
const size_t num_extruders = segmentation.empty() ? 0 : segmentation.front().size();
|
||||
|
||||
struct ByExtruder {
|
||||
ExPolygons expolygons;
|
||||
@@ -875,6 +876,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
||||
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
||||
// Gather per extruder expolygons.
|
||||
assert(segmentation[layer_id].size() == num_extruders);
|
||||
by_extruder.assign(num_extruders, ByExtruder());
|
||||
by_region.assign(layer.region_count(), ByRegion());
|
||||
bool layer_split = false;
|
||||
|
||||
@@ -2432,7 +2432,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
|
||||
std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower);
|
||||
|
||||
// BBS: normalize painting data with current filament count
|
||||
// BBS: normalize painting data with current available filament count
|
||||
// (physical + enabled mixed/virtual filaments).
|
||||
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) {
|
||||
const ModelObject& model_object = *m_model->objects[obj_idx];
|
||||
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++volume_idx) {
|
||||
@@ -2441,7 +2442,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
continue;
|
||||
|
||||
unsigned int filaments_count = (unsigned int)dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"))->values.size();
|
||||
model_volume.update_extruder_count(filaments_count);
|
||||
size_t available_filaments = filaments_count;
|
||||
if (wxGetApp().preset_bundle != nullptr)
|
||||
available_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(filaments_count);
|
||||
|
||||
model_volume.update_extruder_count(available_filaments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1488,14 +1488,18 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men
|
||||
if (item_id != wxNOT_FOUND)
|
||||
menu->Destroy(item_id);
|
||||
|
||||
wxMenu* sub_menu = new wxMenu();
|
||||
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
|
||||
int filaments_cnt = icons.size();
|
||||
wxMenu * sub_menu = new wxMenu();
|
||||
std::vector<wxBitmap *> icons = get_extruder_color_icons(true);
|
||||
const std::vector<std::string> &filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
int filaments_cnt = std::max(wxGetApp().filaments_cnt(), 0);
|
||||
filaments_cnt = std::min(filaments_cnt, static_cast<int>(icons.size()));
|
||||
filaments_cnt = std::min(filaments_cnt, static_cast<int>(filament_presets.size()));
|
||||
|
||||
for (int i = 0; i < filaments_cnt; i++) {
|
||||
if (i == active_filament_menu_id)
|
||||
continue;
|
||||
|
||||
auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]);
|
||||
auto preset = wxGetApp().preset_bundle->filaments.find_preset(filament_presets[i]);
|
||||
wxString item_name = preset ? from_u8(preset->label(false)) : wxString::Format(_L("Filament %d"), i + 1);
|
||||
|
||||
append_menu_item(
|
||||
|
||||
@@ -70,7 +70,13 @@ static DynamicPrintConfig& printer_config()
|
||||
|
||||
static int filaments_count()
|
||||
{
|
||||
return wxGetApp().filaments_cnt();
|
||||
if (wxGetApp().preset_bundle == nullptr)
|
||||
return 0;
|
||||
|
||||
int physical = wxGetApp().filaments_cnt();
|
||||
// Include enabled mixed (virtual) filaments in the count.
|
||||
const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments;
|
||||
return static_cast<int>(mixed_mgr.total_filaments(physical));
|
||||
}
|
||||
|
||||
static void take_snapshot(const std::string& snapshot_name)
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <algorithm>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@@ -770,6 +772,29 @@ void GLGizmoMmuSegmentation::update_model_object()
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
const size_t num_physical = static_cast<size_t>(std::max(wxGetApp().filaments_cnt(), 0));
|
||||
size_t num_total = num_physical;
|
||||
if (wxGetApp().preset_bundle != nullptr)
|
||||
num_total = wxGetApp().preset_bundle->mixed_filaments.total_filaments(num_physical);
|
||||
|
||||
size_t max_used_state = 0;
|
||||
for (const ModelVolume *mv : mo->volumes) {
|
||||
if (!mv->is_model_part())
|
||||
continue;
|
||||
const auto &used_states = mv->mmu_segmentation_facets.get_data().used_states;
|
||||
for (size_t state_idx = static_cast<size_t>(EnforcerBlockerType::Extruder1); state_idx < used_states.size(); ++state_idx) {
|
||||
if (used_states[state_idx])
|
||||
max_used_state = std::max(max_used_state, state_idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (max_used_state > num_physical) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GLGizmoMmuSegmentation::update_model_object painted virtual extruder state detected"
|
||||
<< " max_used_state=" << max_used_state
|
||||
<< " physical_filaments=" << num_physical
|
||||
<< " total_filaments=" << num_total;
|
||||
}
|
||||
|
||||
const ModelObjectPtrs &mos = wxGetApp().model().objects;
|
||||
size_t obj_idx = std::find(mos.begin(), mos.end(), mo) - mos.begin();
|
||||
wxGetApp().obj_list()->update_info_items(obj_idx);
|
||||
|
||||
+158
-4
@@ -1,5 +1,6 @@
|
||||
#include "Plater.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/MixedFilament.hpp"
|
||||
#include "common_func/common_func.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -42,6 +43,7 @@
|
||||
#include <wx/popupwin.h>
|
||||
#endif
|
||||
#include <wx/clrpicker.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/tokenzr.h>
|
||||
#include <wx/aui/aui.h>
|
||||
|
||||
@@ -634,6 +636,10 @@ struct Sidebar::priv
|
||||
int m_menu_filament_id = -1;
|
||||
wxPanel* m_panel_filament_content;
|
||||
wxScrolledWindow* m_scrolledWindow_filament_content;
|
||||
|
||||
// Mixed (virtual) filaments panel
|
||||
wxPanel* m_panel_mixed_filaments = nullptr;
|
||||
wxBoxSizer* m_sizer_mixed_filaments = nullptr;
|
||||
wxStaticLine* m_staticline2;
|
||||
wxPanel* m_panel_project_title;
|
||||
ScalableButton* m_filament_icon = nullptr;
|
||||
@@ -1266,7 +1272,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
ConfigOptionFloat* flush_multi_opt = project_config.option<ConfigOptionFloat>("flush_multiplier");
|
||||
float flush_multiplier = flush_multi_opt ? flush_multi_opt->getFloat() : 1.f;
|
||||
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
const auto& full_config = wxGetApp().preset_bundle->full_config();
|
||||
const auto& extra_flush_volumes = get_min_flush_volumes(full_config);
|
||||
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours, extra_flush_volumes, flush_multiplier);
|
||||
@@ -1423,6 +1429,23 @@ Sidebar::Sidebar(Plater *parent)
|
||||
scrolled_sizer->Add(p->m_panel_filament_content, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
// --- Mixed Colours Panel ---
|
||||
{
|
||||
p->m_panel_mixed_filaments = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
p->m_panel_mixed_filaments->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
p->m_sizer_mixed_filaments = new wxBoxSizer(wxVERTICAL);
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
|
||||
// Title
|
||||
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors"));
|
||||
mixed_title->SetFont(Label::Head_14);
|
||||
p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
|
||||
p->m_panel_mixed_filaments->SetSizer(p->m_sizer_mixed_filaments);
|
||||
p->m_panel_mixed_filaments->Layout();
|
||||
p->m_panel_mixed_filaments->Hide(); // Hidden until 2+ filaments
|
||||
scrolled_sizer->Add(p->m_panel_mixed_filaments, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
{
|
||||
//add project title
|
||||
auto params_panel = ((MainFrame*)parent->GetParent())->m_param_panel;
|
||||
@@ -2161,6 +2184,116 @@ void Sidebar::on_filaments_change(size_t num_filaments)
|
||||
p->m_panel_filament_title->Refresh();
|
||||
update_ui_from_settings();
|
||||
update_dynamic_filament_list();
|
||||
update_mixed_filament_panel();
|
||||
}
|
||||
|
||||
void Sidebar::update_mixed_filament_panel()
|
||||
{
|
||||
if (!p->m_panel_mixed_filaments || !p->m_sizer_mixed_filaments)
|
||||
return;
|
||||
|
||||
auto *preset_bundle = wxGetApp().preset_bundle;
|
||||
if (!preset_bundle)
|
||||
return;
|
||||
|
||||
const auto &mixed_mgr = preset_bundle->mixed_filaments;
|
||||
const auto &mixed = mixed_mgr.mixed_filaments();
|
||||
|
||||
// Clear old entries safely. Using Clear(true) avoids manual child-window
|
||||
// destruction loops with deferred wxWindow::Destroy().
|
||||
p->m_sizer_mixed_filaments->Clear(true);
|
||||
|
||||
// Recreate header.
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
|
||||
auto *mixed_title = new wxStaticText(p->m_panel_mixed_filaments, wxID_ANY, _L("Mixed Colors"));
|
||||
mixed_title->SetFont(Label::Head_14);
|
||||
p->m_sizer_mixed_filaments->Add(mixed_title, 0, wxLEFT | wxRIGHT, FromDIP(16));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
|
||||
|
||||
if (mixed.empty()) {
|
||||
p->m_panel_mixed_filaments->Hide();
|
||||
Layout();
|
||||
return;
|
||||
}
|
||||
|
||||
int mixed_id = 0;
|
||||
for (const auto &mf : mixed) {
|
||||
// Row panel
|
||||
auto *row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY);
|
||||
row->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
auto *row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Colour swatch
|
||||
wxColour swatch_color(mf.display_color);
|
||||
auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16)));
|
||||
swatch->SetBackgroundColour(swatch_color);
|
||||
swatch->SetMinSize(wxSize(FromDIP(16), FromDIP(16)));
|
||||
row_sizer->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8));
|
||||
|
||||
// Label: "Filament 1 + Filament 2"
|
||||
wxString label_str = wxString::Format("Filament %u + Filament %u",
|
||||
(unsigned)mf.component_a, (unsigned)mf.component_b);
|
||||
auto *label = new wxStaticText(row, wxID_ANY, label_str);
|
||||
row_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8));
|
||||
|
||||
// Ratio display
|
||||
wxString ratio_str = wxString::Format("%d:%d", mf.ratio_a, mf.ratio_b);
|
||||
auto *ratio_label = new wxStaticText(row, wxID_ANY, ratio_str);
|
||||
row_sizer->Add(ratio_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
|
||||
|
||||
// Ratio spin controls
|
||||
auto *spin_a = new wxSpinCtrl(row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(45), -1), wxSP_ARROW_KEYS, 1, 10, mf.ratio_a);
|
||||
auto *spin_b = new wxSpinCtrl(row, wxID_ANY, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(FromDIP(45), -1), wxSP_ARROW_KEYS, 1, 10, mf.ratio_b);
|
||||
row_sizer->Add(spin_a, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(2));
|
||||
auto *colon = new wxStaticText(row, wxID_ANY, ":");
|
||||
row_sizer->Add(colon, 0, wxALIGN_CENTER_VERTICAL);
|
||||
row_sizer->Add(spin_b, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
|
||||
|
||||
// Enable checkbox
|
||||
auto *chk = new wxCheckBox(row, wxID_ANY, wxEmptyString);
|
||||
chk->SetValue(mf.enabled);
|
||||
row_sizer->Add(chk, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(8));
|
||||
|
||||
// Bind events to update the MixedFilamentManager when ratio/enabled changes.
|
||||
int capturedIdx = mixed_id;
|
||||
auto *capturedSpinA = spin_a;
|
||||
auto *capturedSpinB = spin_b;
|
||||
auto *capturedChk = chk;
|
||||
|
||||
auto update_lambda = [capturedIdx, capturedSpinA, capturedSpinB, capturedChk](wxCommandEvent &) {
|
||||
auto &mgr = wxGetApp().preset_bundle->mixed_filaments;
|
||||
auto &mfs = mgr.mixed_filaments();
|
||||
if (capturedIdx < (int)mfs.size()) {
|
||||
mfs[capturedIdx].ratio_a = capturedSpinA->GetValue();
|
||||
mfs[capturedIdx].ratio_b = capturedSpinB->GetValue();
|
||||
mfs[capturedIdx].enabled = capturedChk->GetValue();
|
||||
// Recompute display color.
|
||||
ConfigOptionStrings *co = wxGetApp().preset_bundle->project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
if (co && mfs[capturedIdx].component_a <= co->values.size() && mfs[capturedIdx].component_b <= co->values.size()) {
|
||||
mfs[capturedIdx].display_color = Slic3r::MixedFilamentManager::blend_color(
|
||||
co->values[mfs[capturedIdx].component_a - 1],
|
||||
co->values[mfs[capturedIdx].component_b - 1],
|
||||
mfs[capturedIdx].ratio_a, mfs[capturedIdx].ratio_b);
|
||||
}
|
||||
}
|
||||
};
|
||||
spin_a->Bind(wxEVT_SPINCTRL, update_lambda);
|
||||
spin_b->Bind(wxEVT_SPINCTRL, update_lambda);
|
||||
chk->Bind(wxEVT_CHECKBOX, update_lambda);
|
||||
|
||||
row->SetSizer(row_sizer);
|
||||
p->m_sizer_mixed_filaments->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(8));
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
|
||||
|
||||
++mixed_id;
|
||||
}
|
||||
|
||||
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(8));
|
||||
p->m_panel_mixed_filaments->Show();
|
||||
p->m_panel_mixed_filaments->Layout();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Sidebar::add_filament() {
|
||||
@@ -2845,7 +2978,7 @@ void Sidebar::auto_calc_flushing_volumes(const int modify_id)
|
||||
int m_max_flush_volume = Slic3r::g_max_flush_volume;
|
||||
unsigned int m_number_of_extruders = (int)(sqrt(init_matrix.size()) + 0.001);
|
||||
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
std::vector<std::vector<wxColour>> multi_colours;
|
||||
|
||||
// Support for multi-color filament
|
||||
@@ -8377,6 +8510,10 @@ void Plater::priv::on_filament_color_changed(wxCommandEvent &event)
|
||||
if (wxGetApp().app_config->get("auto_calculate") == "true") {
|
||||
sidebar->auto_calc_flushing_volumes(modify_id);
|
||||
}
|
||||
|
||||
// Regenerate mixed filaments and update the sidebar panel.
|
||||
wxGetApp().preset_bundle->update_multi_material_filament_presets();
|
||||
sidebar->update_mixed_filament_panel();
|
||||
}
|
||||
|
||||
void Plater::priv::install_network_plugin(wxCommandEvent &event)
|
||||
@@ -14238,9 +14375,13 @@ void Plater::on_filaments_change(size_t num_filaments)
|
||||
part_plate->update_first_layer_print_sequence(num_filaments);
|
||||
}
|
||||
|
||||
size_t total_filaments = num_filaments;
|
||||
if (wxGetApp().preset_bundle != nullptr)
|
||||
total_filaments = wxGetApp().preset_bundle->mixed_filaments.total_filaments(num_filaments);
|
||||
|
||||
for (ModelObject* mo : wxGetApp().model().objects) {
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
mv->update_extruder_count(num_filaments);
|
||||
mv->update_extruder_count(total_filaments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14442,17 +14583,30 @@ void Plater::on_activate()
|
||||
}
|
||||
|
||||
// Get vector of extruder colors considering filament color, if extruder color is undefined.
|
||||
std::vector<std::string> Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const
|
||||
std::vector<std::string> Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result, bool include_mixed) const
|
||||
{
|
||||
if (wxGetApp().is_gcode_viewer() && result != nullptr)
|
||||
return result->extruder_colors;
|
||||
else {
|
||||
if (wxGetApp().preset_bundle == nullptr)
|
||||
return {};
|
||||
|
||||
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config;
|
||||
std::vector<std::string> filament_colors;
|
||||
if (!config->has("filament_colour")) // in case of a SLA print
|
||||
return filament_colors;
|
||||
|
||||
filament_colors = (config->option<ConfigOptionStrings>("filament_colour"))->values;
|
||||
const size_t num_physical = static_cast<size_t>(std::max(wxGetApp().filaments_cnt(), 0));
|
||||
filament_colors.resize(num_physical, "#26A69A");
|
||||
|
||||
if (include_mixed) {
|
||||
// Append display colours for enabled mixed (virtual) filaments.
|
||||
const auto &mixed_mgr = wxGetApp().preset_bundle->mixed_filaments;
|
||||
for (const auto &dc : mixed_mgr.display_colors())
|
||||
filament_colors.push_back(dc);
|
||||
}
|
||||
|
||||
return filament_colors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ public:
|
||||
void edit_filament();
|
||||
|
||||
void on_filaments_delete(size_t filament_id);
|
||||
void update_mixed_filament_panel();
|
||||
// BBS
|
||||
void on_bed_type_change(BedType bed_type);
|
||||
void load_ams_list(std::string const & device, MachineObject* obj);
|
||||
@@ -499,7 +500,7 @@ public:
|
||||
void force_print_bed_update();
|
||||
// On activating the parent window.
|
||||
void on_activate();
|
||||
std::vector<std::string> get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const;
|
||||
std::vector<std::string> get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr, bool include_mixed = true) const;
|
||||
std::vector<std::string> get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const;
|
||||
|
||||
void update_menus();
|
||||
|
||||
@@ -1702,7 +1702,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
// -1 means caculate all
|
||||
auto update_flush_volume = [](int idx = -1) {
|
||||
if (idx < 0) {
|
||||
size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config().size();
|
||||
size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false).size();
|
||||
for (size_t i = 0; i < filament_size; ++i)
|
||||
wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(i);
|
||||
}
|
||||
@@ -5347,8 +5347,7 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
if (m_type == Preset::TYPE_PRINTER && wxGetApp().app_config->get_bool("remember_printer_config")) {
|
||||
if (preset_name.find("Snapmaker U1") != std::string::npos) {
|
||||
// 在 update_selections() 改变耗材数量之前先保存旧数量和颜色
|
||||
size_t old_filament_count = m_preset_bundle->filament_presets.size();
|
||||
std::vector<std::string> old_filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
std::vector<std::string> old_filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
std::vector<std::string> old_filament_presets = m_preset_bundle->filament_presets;
|
||||
|
||||
m_preset_bundle->update_selections(*wxGetApp().app_config);
|
||||
|
||||
Reference in New Issue
Block a user