mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
Collapse same-color mixed filament regions across painted areas
Resolve ordinary mixed-filament painted regions to their effective physical filament on each layer so adjacent same-color zones can merge into one continuous area instead of being sliced as separate islands. Add a global "Collapse same-color mixed regions" option, enabled by default, and automatically turn it off when Local Z dithering is enabled while still allowing manual re-enable for experimental use.
This commit is contained in:
@@ -1321,6 +1321,43 @@ unsigned int MixedFilamentManager::resolve_perimeter(unsigned int filament_id,
|
||||
return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height, force_height_weighted);
|
||||
}
|
||||
|
||||
unsigned int MixedFilamentManager::effective_painted_region_filament_id(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z,
|
||||
float layer_height,
|
||||
float layer_height_a,
|
||||
float layer_height_b,
|
||||
float base_layer_height) const
|
||||
{
|
||||
const int mixed_idx = mixed_index_from_filament_id(filament_id, num_physical);
|
||||
if (mixed_idx < 0)
|
||||
return filament_id;
|
||||
|
||||
const MixedFilament &mf = m_mixed[size_t(mixed_idx)];
|
||||
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
|
||||
return filament_id;
|
||||
|
||||
const std::string normalized_pattern = normalize_manual_pattern(mf.manual_pattern);
|
||||
if (normalized_pattern.find(',') != std::string::npos)
|
||||
return filament_id;
|
||||
|
||||
const bool is_custom_mixed = mf.custom;
|
||||
if (!is_custom_mixed && (layer_height_a > 0.f || layer_height_b > 0.f)) {
|
||||
const float safe_base = std::max<float>(0.01f, base_layer_height);
|
||||
const int ratio_a = std::max(1, int(std::lround((layer_height_a > 0.f ? layer_height_a : safe_base) / safe_base)));
|
||||
const int ratio_b = std::max(1, int(std::lround((layer_height_b > 0.f ? layer_height_b : safe_base) / safe_base)));
|
||||
const int cycle = ratio_a + ratio_b;
|
||||
|
||||
if (cycle > 0) {
|
||||
const int pos = ((layer_index % cycle) + cycle) % cycle;
|
||||
return pos < ratio_a ? mf.component_a : mf.component_b;
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(filament_id, num_physical, layer_index, layer_print_z, layer_height);
|
||||
}
|
||||
|
||||
std::vector<unsigned int> MixedFilamentManager::ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
|
||||
@@ -179,6 +179,18 @@ public:
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
bool force_height_weighted = false) const;
|
||||
// Resolve the filament ID that should own painted regions on this layer.
|
||||
// Modes that require virtual identity later in G-code generation keep the
|
||||
// original mixed ID; ordinary mixed rows collapse to the current physical
|
||||
// extruder so adjacent same-tool regions can merge.
|
||||
unsigned int effective_painted_region_filament_id(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
float layer_print_z = 0.f,
|
||||
float layer_height = 0.f,
|
||||
float layer_height_a = 0.f,
|
||||
float layer_height_b = 0.f,
|
||||
float base_layer_height = 0.2f) const;
|
||||
std::vector<unsigned int> ordered_perimeter_extruders(unsigned int filament_id,
|
||||
size_t num_physical,
|
||||
int layer_index,
|
||||
|
||||
@@ -75,6 +75,7 @@ static std::vector<std::string> s_project_options {
|
||||
"mixed_filament_height_upper_bound",
|
||||
"mixed_filament_advanced_dithering",
|
||||
"mixed_filament_surface_indentation",
|
||||
"mixed_filament_region_collapse",
|
||||
"mixed_filament_definitions",
|
||||
"mixed_color_layer_height_a",
|
||||
"mixed_color_layer_height_b",
|
||||
|
||||
@@ -297,6 +297,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "mixed_filament_height_upper_bound"
|
||||
|| opt_key == "mixed_filament_advanced_dithering"
|
||||
|| opt_key == "mixed_filament_surface_indentation"
|
||||
|| opt_key == "mixed_filament_region_collapse"
|
||||
|| opt_key == "mixed_filament_definitions"
|
||||
// Spiral Vase forces different kind of slicing than the normal model:
|
||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||
|
||||
@@ -1112,18 +1112,22 @@ static inline void append_unique_painted_extruder(std::vector<unsigned int> &pai
|
||||
painting_extruders.emplace_back(extruder_id);
|
||||
}
|
||||
|
||||
static void append_same_layer_component_extruders(const MixedFilamentManager &mixed_mgr,
|
||||
unsigned int state_id,
|
||||
size_t num_physical_extruders,
|
||||
std::vector<unsigned int> &painting_extruders)
|
||||
static void append_mixed_component_extruders(const MixedFilamentManager &mixed_mgr,
|
||||
unsigned int state_id,
|
||||
size_t num_physical_extruders,
|
||||
std::vector<unsigned int> &painting_extruders)
|
||||
{
|
||||
if (state_id <= num_physical_extruders)
|
||||
return;
|
||||
|
||||
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(state_id, num_physical_extruders);
|
||||
if (mixed_row == nullptr || !mixed_row->enabled || mixed_row->distribution_mode != int(MixedFilament::SameLayerPointillisme))
|
||||
if (mixed_row == nullptr || !mixed_row->enabled)
|
||||
return;
|
||||
|
||||
// Pre-create painted target regions for every physical filament a mixed row
|
||||
// may resolve to. apply_mm_segmentation can then collapse ordinary mixed
|
||||
// channels onto the active physical tool for a layer without losing the
|
||||
// destination region.
|
||||
append_unique_painted_extruder(painting_extruders, mixed_row->component_a, num_physical_extruders);
|
||||
append_unique_painted_extruder(painting_extruders, mixed_row->component_b, num_physical_extruders);
|
||||
|
||||
@@ -1177,6 +1181,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
new_full_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
new_full_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
new_full_config.option("mixed_filament_surface_indentation", true);
|
||||
new_full_config.option("mixed_filament_region_collapse", true);
|
||||
new_full_config.option("mixed_filament_definitions", true);
|
||||
m_config.option("dithering_z_step_size", true);
|
||||
m_config.option("dithering_local_z_mode", true);
|
||||
@@ -1188,6 +1193,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
m_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
m_config.option("mixed_filament_surface_indentation", true);
|
||||
m_config.option("mixed_filament_region_collapse", true);
|
||||
m_config.option("mixed_filament_definitions", true);
|
||||
m_default_object_config.option("dithering_z_step_size", true);
|
||||
m_default_object_config.option("dithering_local_z_mode", true);
|
||||
@@ -1199,6 +1205,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_object_config.option("mixed_filament_pointillism_pixel_size", true);
|
||||
m_default_object_config.option("mixed_filament_pointillism_line_gap", true);
|
||||
m_default_object_config.option("mixed_filament_surface_indentation", true);
|
||||
m_default_object_config.option("mixed_filament_region_collapse", true);
|
||||
m_default_object_config.option("mixed_filament_definitions", true);
|
||||
// BBS
|
||||
int used_filaments = this->extruders(true).size();
|
||||
@@ -1772,10 +1779,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
continue;
|
||||
if (state_idx <= num_total_filaments) {
|
||||
painting_extruders.emplace_back(static_cast<unsigned int>(state_idx));
|
||||
append_same_layer_component_extruders(m_mixed_filament_mgr,
|
||||
static_cast<unsigned int>(state_idx),
|
||||
num_extruders,
|
||||
painting_extruders);
|
||||
append_mixed_component_extruders(m_mixed_filament_mgr,
|
||||
static_cast<unsigned int>(state_idx),
|
||||
num_extruders,
|
||||
painting_extruders);
|
||||
} else
|
||||
++dropped_painted_states;
|
||||
}
|
||||
|
||||
@@ -4244,6 +4244,15 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.0));
|
||||
|
||||
def = this->add("mixed_filament_region_collapse", coBool);
|
||||
def->label = L("Collapse same-color mixed regions");
|
||||
def->category = L("Others");
|
||||
def->tooltip = L("Merge ordinary mixed-filament painted regions into a single area when they resolve to the same physical filament on a layer.\n\n"
|
||||
"This improves continuity for adjacent same-color areas. Local Z dithering turns this off automatically when enabled, but you may turn it back on manually.\n\n"
|
||||
"Experimental with Local Z dithering.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("mixed_filament_definitions", coString);
|
||||
def->label = L("Mixed filament custom definitions");
|
||||
def->tooltip = L("Serialized custom mixed filament rows.\n\n"
|
||||
|
||||
@@ -1363,6 +1363,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloat, mixed_filament_pointillism_pixel_size))
|
||||
((ConfigOptionFloat, mixed_filament_pointillism_line_gap))
|
||||
((ConfigOptionFloat, mixed_filament_surface_indentation))
|
||||
((ConfigOptionBool, mixed_filament_region_collapse))
|
||||
((ConfigOptionString, mixed_filament_definitions))
|
||||
((ConfigOptionFloat, dithering_z_step_size))
|
||||
((ConfigOptionBool, dithering_local_z_mode))
|
||||
|
||||
@@ -950,6 +950,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "dithering_z_step_size"
|
||||
|| opt_key == "dithering_local_z_mode"
|
||||
|| opt_key == "dithering_step_painted_zones_only"
|
||||
|| opt_key == "mixed_filament_region_collapse"
|
||||
|| opt_key == "mmu_segmented_region_max_width"
|
||||
|| opt_key == "mmu_segmented_region_interlocking_depth"
|
||||
|| opt_key == "raft_layers"
|
||||
@@ -973,6 +974,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "mixed_filament_height_upper_bound"
|
||||
|| opt_key == "mixed_filament_advanced_dithering"
|
||||
|| opt_key == "mixed_filament_surface_indentation"
|
||||
|| opt_key == "mixed_filament_region_collapse"
|
||||
|| opt_key == "mixed_filament_definitions") {
|
||||
// Mixed filament gradient controls affect layer cadence and virtual
|
||||
// tool distribution, so force a re-slice prompt like other
|
||||
|
||||
@@ -2947,9 +2947,21 @@ template<typename ThrowOnCancel>
|
||||
static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> segmentation, ThrowOnCancel throw_on_cancel)
|
||||
{
|
||||
assert(segmentation.size() == print_object.layer_count());
|
||||
const PrintConfig &print_cfg = print_object.print()->config();
|
||||
const DynamicPrintConfig &full_cfg = print_object.print()->full_print_config();
|
||||
const size_t num_physical = print_cfg.filament_diameter.size();
|
||||
const coordf_t preferred_a = float_from_full_config(full_cfg, "mixed_color_layer_height_a",
|
||||
coordf_t(print_cfg.mixed_color_layer_height_a.value));
|
||||
const coordf_t preferred_b = float_from_full_config(full_cfg, "mixed_color_layer_height_b",
|
||||
coordf_t(print_cfg.mixed_color_layer_height_b.value));
|
||||
const coordf_t base_height = std::max<coordf_t>(0.01f, coordf_t(print_object.config().layer_height.value));
|
||||
const bool collapse_mixed_regions =
|
||||
bool_from_full_config(full_cfg, "mixed_filament_region_collapse", print_cfg.mixed_filament_region_collapse.value);
|
||||
const MixedFilamentManager &mixed_mgr = print_object.print()->mixed_filament_manager();
|
||||
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
|
||||
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||
[&print_object, &segmentation, &mixed_mgr, num_physical, preferred_a, preferred_b, base_height, collapse_mixed_regions, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||
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);
|
||||
@@ -2982,7 +2994,20 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
size_t missing_target_regions = 0;
|
||||
std::vector<int> missing_target_extruders;
|
||||
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
|
||||
ByExtruder ®ion = by_extruder[extruder_id];
|
||||
const unsigned int channel_id = unsigned(extruder_id + 1);
|
||||
const unsigned int effective_filament_id = collapse_mixed_regions ?
|
||||
mixed_mgr.effective_painted_region_filament_id(channel_id,
|
||||
num_physical,
|
||||
int(layer_id),
|
||||
float(layer.print_z),
|
||||
float(layer.height),
|
||||
float(preferred_a),
|
||||
float(preferred_b),
|
||||
float(base_height)) :
|
||||
channel_id;
|
||||
const size_t effective_idx =
|
||||
effective_filament_id >= 1 && effective_filament_id <= num_extruders ? size_t(effective_filament_id - 1) : extruder_id;
|
||||
ByExtruder ®ion = by_extruder[effective_idx];
|
||||
append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
|
||||
if (! region.expolygons.empty()) {
|
||||
region.bbox = get_extents(region.expolygons);
|
||||
|
||||
@@ -1491,6 +1491,21 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
update_wiping_button_visibility();
|
||||
}
|
||||
|
||||
if (opt_key == "dithering_local_z_mode" &&
|
||||
boost::any_cast<bool>(value) &&
|
||||
(!m_config->has("mixed_filament_region_collapse") ||
|
||||
m_config->option("mixed_filament_region_collapse") == nullptr ||
|
||||
m_config->opt_bool("mixed_filament_region_collapse"))) {
|
||||
change_opt_value(*m_config, "mixed_filament_region_collapse", boost::any(false));
|
||||
if (m_type == Preset::TYPE_PRINT) {
|
||||
DynamicPrintConfig &project_cfg = wxGetApp().preset_bundle->project_config;
|
||||
project_cfg.set_key_value("mixed_filament_region_collapse", new ConfigOptionBool(false));
|
||||
}
|
||||
if (Field *field = this->get_field("mixed_filament_region_collapse"))
|
||||
field->set_value(boost::any(false), false);
|
||||
update_dirty();
|
||||
}
|
||||
|
||||
|
||||
if (opt_key == "single_extruder_multi_material" ){
|
||||
const auto bSEMM = m_config->opt_bool("single_extruder_multi_material");
|
||||
@@ -1791,6 +1806,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
opt_key == "mixed_filament_pointillism_pixel_size" ||
|
||||
opt_key == "mixed_filament_pointillism_line_gap" ||
|
||||
opt_key == "mixed_filament_surface_indentation" ||
|
||||
opt_key == "mixed_filament_region_collapse" ||
|
||||
opt_key == "dithering_z_step_size" ||
|
||||
opt_key == "dithering_local_z_mode" ||
|
||||
opt_key == "dithering_step_painted_zones_only" ||
|
||||
@@ -2528,6 +2544,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
|
||||
optgroup->append_single_option_line("mixed_filament_pointillism_pixel_size");
|
||||
optgroup->append_single_option_line("mixed_filament_pointillism_line_gap");
|
||||
optgroup->append_single_option_line("mixed_filament_surface_indentation");
|
||||
optgroup->append_single_option_line("mixed_filament_region_collapse");
|
||||
optgroup->append_single_option_line("dithering_z_step_size");
|
||||
optgroup->append_single_option_line("dithering_local_z_mode");
|
||||
optgroup->append_single_option_line("dithering_step_painted_zones_only");
|
||||
|
||||
@@ -221,6 +221,41 @@ TEST_CASE("Mixed filament perimeter resolver uses grouped manual patterns by ins
|
||||
CHECK(ordered_layer1[1] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Mixed filament painted-region resolver collapses ordinary mixed rows to the active physical extruder", "[MixedFilament]")
|
||||
{
|
||||
const std::vector<std::string> colors = {"#FF0000", "#00FF00"};
|
||||
|
||||
MixedFilamentManager mgr;
|
||||
mgr.add_custom_filament(1, 2, 50, colors);
|
||||
REQUIRE(mgr.mixed_filaments().size() == 1);
|
||||
|
||||
MixedFilament &row = mgr.mixed_filaments().front();
|
||||
row.ratio_a = 1;
|
||||
row.ratio_b = 1;
|
||||
row.manual_pattern.clear();
|
||||
row.distribution_mode = int(MixedFilament::Simple);
|
||||
|
||||
CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 1);
|
||||
CHECK(mgr.effective_painted_region_filament_id(3, 2, 1) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Mixed filament painted-region resolver preserves virtual channels for grouped and same-layer modes", "[MixedFilament]")
|
||||
{
|
||||
const std::vector<std::string> colors = {"#00FFFF", "#FF00FF"};
|
||||
|
||||
MixedFilamentManager mgr;
|
||||
mgr.add_custom_filament(1, 2, 50, colors);
|
||||
REQUIRE(mgr.mixed_filaments().size() == 1);
|
||||
|
||||
MixedFilament &row = mgr.mixed_filaments().front();
|
||||
row.manual_pattern = MixedFilamentManager::normalize_manual_pattern("12,21");
|
||||
CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 3);
|
||||
|
||||
row.manual_pattern.clear();
|
||||
row.distribution_mode = int(MixedFilament::SameLayerPointillisme);
|
||||
CHECK(mgr.effective_painted_region_filament_id(3, 2, 0) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("ExtrusionPath copies preserve inset index", "[MixedFilament]")
|
||||
{
|
||||
ExtrusionPath src(erPerimeter);
|
||||
|
||||
Reference in New Issue
Block a user