Add changelog for v0.7-alpha pre-release: Introduce experimental features including Local Z reliability improvements, manual pattern mixed filaments, selective expansion/contraction controls, and a refreshed UI for mixed filaments. Enhance configuration options with surface indentation for mixed filament behavior tuning. Update PrintConfig and GUI components to support new features and improve user experience.

This commit is contained in:
Rad
2026-02-13 21:45:31 +01:00
parent c414e377a0
commit 4fea9f5d2c
10 changed files with 392 additions and 203 deletions
+1
View File
@@ -47,6 +47,7 @@ static std::vector<std::string> s_project_options {
"mixed_filament_height_upper_bound",
"mixed_filament_cycle_layers",
"mixed_filament_advanced_dithering",
"mixed_filament_surface_indentation",
"mixed_filament_definitions",
"mixed_color_layer_height_a",
"mixed_color_layer_height_b",
+1
View File
@@ -253,6 +253,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_cycle_layers"
|| opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_surface_indentation"
|| 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.
+8
View File
@@ -1177,6 +1177,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
new_full_config.option("mixed_filament_advanced_dithering", true);
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_definitions", true);
m_config.option("dithering_z_step_size", true);
m_config.option("dithering_local_z_mode", true);
@@ -1188,6 +1189,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_config.option("mixed_filament_advanced_dithering", true);
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_definitions", true);
m_default_object_config.option("dithering_z_step_size", true);
m_default_object_config.option("dithering_local_z_mode", true);
@@ -1199,6 +1201,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_default_object_config.option("mixed_filament_advanced_dithering", true);
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_definitions", true);
// BBS
int used_filaments = this->extruders(true).size();
@@ -1304,6 +1307,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
bool mixed_advanced_dither = false;
float mixed_pointillism_pixel_size = 0.f;
float mixed_pointillism_line_gap = 0.f;
float mixed_surface_indentation = 0.f;
std::string mixed_custom_definitions;
if (new_full_config.has("mixed_filament_gradient_mode")) {
if (const ConfigOptionBool *opt = new_full_config.option<ConfigOptionBool>("mixed_filament_gradient_mode"))
@@ -1327,6 +1331,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
mixed_pointillism_pixel_size = float(new_full_config.opt_float("mixed_filament_pointillism_pixel_size"));
if (new_full_config.has("mixed_filament_pointillism_line_gap"))
mixed_pointillism_line_gap = float(new_full_config.opt_float("mixed_filament_pointillism_line_gap"));
if (new_full_config.has("mixed_filament_surface_indentation"))
mixed_surface_indentation = float(new_full_config.opt_float("mixed_filament_surface_indentation"));
if (new_full_config.has("mixed_filament_definitions"))
mixed_custom_definitions = new_full_config.opt_string("mixed_filament_definitions");
@@ -1336,6 +1342,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
mixed_cycle_layers = std::max(2, mixed_cycle_layers);
mixed_pointillism_pixel_size = std::max(0.f, mixed_pointillism_pixel_size);
mixed_pointillism_line_gap = std::max(0.f, mixed_pointillism_line_gap);
mixed_surface_indentation = std::clamp(mixed_surface_indentation, -2.f, 2.f);
BOOST_LOG_TRIVIAL(info) << "Print::apply mixed settings"
<< ", gradient_mode=" << mixed_gradient_mode
@@ -1345,6 +1352,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
<< ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0)
<< ", pointillism_pixel_size=" << mixed_pointillism_pixel_size
<< ", pointillism_line_gap=" << mixed_pointillism_line_gap
<< ", surface_indentation=" << mixed_surface_indentation
<< ", custom_definitions_len=" << mixed_custom_definitions.size()
<< ", physical_extruders=" << num_extruders;
+16 -2
View File
@@ -4198,7 +4198,8 @@ void PrintConfigDef::init_fff_params()
def->label = L("Pointillisme pixel size");
def->category = L("Others");
def->tooltip = L("Length of one pointillisme segment along an extrusion path for same-layer pointillisme mode. "
"Set to 0 to use automatic nozzle-based sizing.");
"Set to 0 to use automatic nozzle-based sizing.\n\n"
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
@@ -4208,12 +4209,25 @@ void PrintConfigDef::init_fff_params()
def->label = L("Pointillisme line gap");
def->category = L("Others");
def->tooltip = L("Optional non-extruded spacing between adjacent pointillisme segments. "
"Increase carefully to improve separation and print quality.");
"Increase carefully to improve separation and print quality.\n\n"
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results.");
def->sidetext = "mm";
def->min = 0.;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("mixed_filament_surface_indentation", coFloat);
def->label = L("Selective Expansion contraction");
def->category = L("Others");
def->tooltip = L("XY offset applied to mixed-filament painted regions before region assignment.\n\n"
"Positive values contract the mixed zone inward. Negative values expand it outward.\n\n"
"This applies to mixed filament usage in layer cadence, height cadence, same-layer pointillisme, and local Z dithering.");
def->sidetext = "mm";
def->min = -2.0;
def->max = 2.0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("mixed_filament_definitions", coString);
def->label = L("Mixed filament custom definitions");
def->tooltip = L("Serialized custom mixed filament rows.\n\n"
+1
View File
@@ -1360,6 +1360,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, mixed_filament_advanced_dithering))
((ConfigOptionFloat, mixed_filament_pointillism_pixel_size))
((ConfigOptionFloat, mixed_filament_pointillism_line_gap))
((ConfigOptionFloat, mixed_filament_surface_indentation))
((ConfigOptionString, mixed_filament_definitions))
((ConfigOptionFloat, dithering_z_step_size))
((ConfigOptionBool, dithering_local_z_mode))
+1
View File
@@ -973,6 +973,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_cycle_layers"
|| opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_surface_indentation"
|| opt_key == "mixed_filament_definitions") {
// Mixed filament gradient controls affect layer cadence and virtual
// tool distribution, so force a re-slice prompt like other
+219 -65
View File
@@ -871,6 +871,109 @@ static coordf_t float_from_full_config(const DynamicPrintConfig &full_cfg, const
return coordf_t(full_cfg.opt_float(key));
}
static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
{
const Print *print = print_object.print();
if (print == nullptr || segmentation.empty())
return false;
const PrintConfig &print_cfg = print->config();
const DynamicPrintConfig &full_cfg = print->full_print_config();
coordf_t indentation_mm = float_from_full_config(full_cfg, "mixed_filament_surface_indentation",
coordf_t(print_cfg.mixed_filament_surface_indentation.value));
indentation_mm = std::clamp(indentation_mm, coordf_t(-2.f), coordf_t(2.f));
if (std::abs(indentation_mm) <= EPSILON)
return false;
const size_t num_physical = print_cfg.filament_colour.size();
const size_t num_channels = segmentation.front().size();
if (num_channels <= num_physical)
return false;
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
const bool expand_outward = indentation_mm < 0.f;
const float delta_scaled = float(scale_(std::abs(double(indentation_mm))));
if (delta_scaled <= float(EPSILON))
return false;
size_t changed_layers = 0;
size_t changed_states = 0;
size_t emptied_states = 0;
size_t overlap_clipped_states = 0;
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
if (segmentation[layer_id].size() != num_channels)
continue;
bool layer_changed = false;
ExPolygons occupied;
if (expand_outward) {
for (size_t channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
append(occupied, state_masks);
}
if (occupied.size() > 1)
occupied = union_ex(occupied);
}
for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) {
ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
ExPolygons adjusted = offset_ex(state_masks, expand_outward ? delta_scaled : -delta_scaled);
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
if (expand_outward && !adjusted.empty() && !occupied.empty()) {
ExPolygons clipped = diff_ex(adjusted, occupied, ApplySafetyOffset::Yes);
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
++overlap_clipped_states;
adjusted = std::move(clipped);
if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted);
}
state_masks = std::move(adjusted);
if (state_masks.empty())
++emptied_states;
++changed_states;
layer_changed = true;
if (expand_outward && !state_masks.empty()) {
append(occupied, state_masks);
if (occupied.size() > 1)
occupied = union_ex(occupied);
}
}
if (layer_changed)
++changed_layers;
}
if (changed_states == 0)
return false;
BOOST_LOG_TRIVIAL(warning) << "Mixed surface indentation applied"
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
<< " indentation_mm=" << indentation_mm
<< " direction=" << (expand_outward ? "outward" : "inward")
<< " changed_layers=" << changed_layers
<< " changed_states=" << changed_states
<< " emptied_states=" << emptied_states
<< " overlap_clipped_states=" << overlap_clipped_states;
return true;
}
static bool fit_pass_heights_to_interval(std::vector<double> &passes, double base_height, double lo, double hi)
{
if (passes.empty() || base_height <= EPSILON)
@@ -1255,6 +1358,16 @@ static std::vector<unsigned int> pointillism_sequence_for_row(const MixedFilamen
return sequence;
}
static bool local_z_eligible_mixed_row(const MixedFilament &mf)
{
// Local-Z flow-height modulation is intended for custom gradient rows.
// Keep auto-generated premix rows and manual pattern rows on nominal layers.
return mf.enabled &&
mf.custom &&
mf.manual_pattern.empty() &&
mf.distribution_mode != int(MixedFilament::SameLayerPointillisme);
}
static size_t unique_extruder_count(const std::vector<unsigned int> &sequence, size_t num_physical)
{
if (sequence.empty() || num_physical == 0)
@@ -1773,20 +1886,16 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
size_t total_generated_sublayer_cnt = 0;
size_t total_mixed_state_layers = 0;
size_t forced_height_resolve_calls = 0;
size_t forced_height_resolve_non_custom_calls = 0;
size_t forced_height_resolve_invalid_target = 0;
size_t split_passes_total = 0;
size_t split_passes_with_painted_masks = 0;
size_t split_intervals_without_painted_masks = 0;
size_t strict_ab_assignments = 0;
size_t alternating_height_intervals = 0;
size_t gradient_lock_mismatch_layers = 0;
size_t gradient_lock_unset_mixed_layers = 0;
size_t locked_gradient_source_layer = size_t(-1);
size_t locked_gradient_mixed_idx = size_t(-1);
double locked_gradient_h_a = 0.0;
double locked_gradient_h_b = 0.0;
bool locked_gradient_valid = false;
// Preserve per-row local-Z phase across layers so pass orientation does not
// flip when a different mixed row dominates layer pass heights.
std::vector<uint8_t> row_next_component_a(mixed_rows.size(), uint8_t(1));
std::vector<uint8_t> row_phase_initialized(mixed_rows.size(), uint8_t(0));
int cadence_index = 0;
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
@@ -1813,16 +1922,24 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (mixed_mgr.is_mixed(state_id, num_physical)) {
interval.has_mixed_paint = true;
++mixed_state_count;
append(mixed_masks, state_masks);
const double mixed_area = std::abs(area(state_masks));
if (mixed_area > dominant_mixed_area) {
dominant_mixed_area = mixed_area;
const int resolved_mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
dominant_mixed_idx = resolved_mixed_idx >= 0 ? size_t(resolved_mixed_idx) : size_t(-1);
}
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
continue;
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
interval.has_mixed_paint = true;
++mixed_state_count;
append(mixed_masks, state_masks);
const double mixed_area = std::abs(area(state_masks));
if (mixed_area > dominant_mixed_area) {
dominant_mixed_area = mixed_area;
dominant_mixed_idx = size_t(mixed_idx);
}
}
if (dominant_mixed_idx < mixed_rows.size()) {
@@ -1830,25 +1947,6 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
dominant_gradient_h_a, dominant_gradient_h_b);
dominant_gradient_valid = true;
}
if (interval.has_mixed_paint && preferred_a <= EPSILON && preferred_b <= EPSILON) {
if (!locked_gradient_valid && dominant_gradient_valid) {
locked_gradient_valid = true;
locked_gradient_source_layer = layer_id;
locked_gradient_mixed_idx = dominant_mixed_idx;
locked_gradient_h_a = dominant_gradient_h_a;
locked_gradient_h_b = dominant_gradient_h_b;
BOOST_LOG_TRIVIAL(warning) << "Local-Z gradient lock acquired"
<< " object=" << object_name
<< " layer_id=" << layer_id
<< " mixed_idx=" << locked_gradient_mixed_idx
<< " h_a=" << locked_gradient_h_a
<< " h_b=" << locked_gradient_h_b;
}
if (!locked_gradient_valid)
++gradient_lock_unset_mixed_layers;
else if (dominant_gradient_valid && dominant_mixed_idx != locked_gradient_mixed_idx)
++gradient_lock_mismatch_layers;
}
total_mixed_state_layers += mixed_state_count;
if (!mixed_masks.empty())
mixed_masks = union_ex(mixed_masks);
@@ -1870,12 +1968,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
// Local-Z mode should emit an A/B/A/B pattern for mixed regions and
// derive relative heights from mixed-filament gradient bounds.
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
if (locked_gradient_valid) {
pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper,
locked_gradient_h_a, locked_gradient_h_b);
if (pass_heights.size() > 1)
++alternating_height_intervals;
} else if (dominant_gradient_valid) {
if (dominant_gradient_valid) {
pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper,
dominant_gradient_h_a, dominant_gradient_h_b);
if (pass_heights.size() > 1)
@@ -1890,8 +1983,59 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
else
pass_heights.emplace_back(interval.base_height);
// Keep auto local-Z 2-pass cadence order stable across layers even if the
// dominant mixed row changes. Per-row phase assignment still controls
// which filament gets pass-0 vs pass-1.
if (interval.has_mixed_paint &&
preferred_a <= EPSILON &&
preferred_b <= EPSILON &&
pass_heights.size() == 2 &&
pass_heights[0] > pass_heights[1]) {
std::swap(pass_heights[0], pass_heights[1]);
}
const bool split_interval = interval.has_mixed_paint && pass_heights.size() > 1;
if (split_interval) {
// Derive per-row start phase against the selected layer pass heights so
// complementary mixed rows can coexist on the same nominal layer.
std::vector<uint8_t> start_with_component_a = row_next_component_a;
std::vector<uint8_t> row_seen_in_interval(mixed_rows.size(), uint8_t(0));
if (preferred_a <= EPSILON && preferred_b <= EPSILON) {
for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) {
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
if (state_masks.empty())
continue;
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
continue;
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
row_seen_in_interval[size_t(mixed_idx)] = uint8_t(1);
if (row_phase_initialized[size_t(mixed_idx)] != 0)
continue;
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_component_a[size_t(mixed_idx)] = uint8_t(0);
}
}
++split_intervals;
double z_cursor = interval.z_lo;
size_t pass_idx = 0;
@@ -1923,19 +2067,24 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
++forced_height_resolve_calls;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom)
++forced_height_resolve_non_custom_calls;
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size())
continue;
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (!local_z_eligible_mixed_row(mf))
continue;
row_seen_in_interval[size_t(mixed_idx)] = uint8_t(1);
++forced_height_resolve_calls;
unsigned int target_extruder = 0;
if (mixed_idx >= 0 && size_t(mixed_idx) < mixed_rows.size()) {
const MixedFilament &mf = mixed_rows[size_t(mixed_idx)];
if (mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical) {
// Enforce strict per-pass alternation inside split local-Z intervals.
target_extruder = ((pass_idx % 2) == 0) ? mf.component_a : mf.component_b;
++strict_ab_assignments;
}
if (mf.component_a > 0 && mf.component_a <= num_physical &&
mf.component_b > 0 && mf.component_b <= num_physical) {
const bool start_a = start_with_component_a[size_t(mixed_idx)] != 0;
const bool even_pass = (pass_idx % 2) == 0;
// Enforce strict per-pass alternation inside split local-Z intervals.
target_extruder = even_pass
? (start_a ? mf.component_a : mf.component_b)
: (start_a ? mf.component_b : mf.component_a);
++strict_ab_assignments;
}
if (target_extruder == 0) {
target_extruder = mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
@@ -1965,6 +2114,18 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
++cadence_index;
z_cursor = z_next;
}
if (pass_idx > 0) {
const bool toggle_after_interval = (pass_idx % 2) != 0;
for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) {
if (row_seen_in_interval[mixed_idx] == 0)
continue;
const bool start_a = start_with_component_a[mixed_idx] != 0;
const bool next_a = toggle_after_interval ? !start_a : start_a;
row_next_component_a[mixed_idx] = next_a ? uint8_t(1) : uint8_t(0);
row_phase_initialized[mixed_idx] = uint8_t(1);
}
}
if (!interval_has_split_painted_masks)
++split_intervals_without_painted_masks;
} else {
@@ -1989,10 +2150,10 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
const unsigned int state_id = unsigned(channel_idx + 1);
if (!mixed_mgr.is_mixed(state_id, num_physical))
continue;
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(state_id, num_physical);
if (mixed_row == nullptr || !local_z_eligible_mixed_row(*mixed_row))
continue;
++forced_height_resolve_calls;
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom)
++forced_height_resolve_non_custom_calls;
const unsigned int target_extruder =
mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true);
if (target_extruder == 0 || target_extruder > num_physical) {
@@ -2046,15 +2207,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
<< " strict_ab_assignments=" << strict_ab_assignments
<< " mixed_state_layers=" << total_mixed_state_layers
<< " forced_height_resolve_calls=" << forced_height_resolve_calls
<< " forced_height_resolve_non_custom_calls=" << forced_height_resolve_non_custom_calls
<< " forced_height_resolve_invalid_target=" << forced_height_resolve_invalid_target
<< " gradient_lock_valid=" << locked_gradient_valid
<< " gradient_lock_source_layer=" << locked_gradient_source_layer
<< " gradient_lock_mixed_idx=" << locked_gradient_mixed_idx
<< " gradient_lock_h_a=" << locked_gradient_h_a
<< " gradient_lock_h_b=" << locked_gradient_h_b
<< " gradient_lock_mismatch_layers=" << gradient_lock_mismatch_layers
<< " gradient_lock_unset_mixed_layers=" << gradient_lock_unset_mixed_layers
<< " mixed_lower=" << mixed_lower
<< " mixed_upper=" << mixed_upper
<< " preferred_a=" << preferred_a
@@ -2474,6 +2627,7 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation";
std::vector<std::vector<ExPolygons>> mm_segmentation = multi_material_segmentation_by_painting(*this, [print]() { print->throw_if_canceled(); });
apply_mixed_surface_indentation(*this, mm_segmentation);
// Same-layer pointillisme is applied in G-code path domain (segment-level assignment),
// not by XY state mask splitting, to avoid boolean-induced voids.
BOOST_LOG_TRIVIAL(info) << "Same-layer pointillisme uses path-domain G-code segmentation";
+68 -136
View File
@@ -2215,7 +2215,7 @@ public:
, m_value(std::clamp(value_percent, 0, 100))
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
SetMinSize(wxSize(FromDIP(96), FromDIP(14)));
SetMinSize(wxSize(FromDIP(96), FromDIP(12)));
Bind(wxEVT_PAINT, &MixedGradientSelector::on_paint, this);
Bind(wxEVT_LEFT_DOWN, &MixedGradientSelector::on_left_down, this);
Bind(wxEVT_LEFT_UP, &MixedGradientSelector::on_left_up, this);
@@ -2785,7 +2785,7 @@ public:
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
SetMinSize(wxSize(FromDIP(132), FromDIP(24)));
SetMinSize(wxSize(FromDIP(120), FromDIP(20)));
Bind(wxEVT_PAINT, &MixedMixPreview::on_paint, this);
}
@@ -3254,6 +3254,7 @@ void Sidebar::update_mixed_filament_panel()
int cycle_layers = std::max(2, get_mixed_int("mixed_filament_cycle_layers", 4));
float pointillism_pixel_size = std::max(0.f, get_mixed_float("mixed_filament_pointillism_pixel_size", 0.f));
float pointillism_line_gap = std::max(0.f, get_mixed_float("mixed_filament_pointillism_line_gap", 0.f));
float mixed_surface_indentation = std::clamp(get_mixed_float("mixed_filament_surface_indentation", 0.f), -2.f, 2.f);
bool advanced_dithering = get_mixed_bool("mixed_filament_advanced_dithering", false);
const std::string mixed_definitions = get_mixed_string("mixed_filament_definitions");
@@ -3273,20 +3274,27 @@ void Sidebar::update_mixed_filament_panel()
set_mixed_int("mixed_filament_cycle_layers", cycle_layers);
set_mixed_float("mixed_filament_pointillism_pixel_size", pointillism_pixel_size);
set_mixed_float("mixed_filament_pointillism_line_gap", pointillism_line_gap);
set_mixed_float("mixed_filament_surface_indentation", mixed_surface_indentation);
set_mixed_string("mixed_filament_definitions", mixed_mgr.serialize_custom_entries());
}
auto &mixed = mixed_mgr.mixed_filaments();
const int compact_gap_x = FromDIP(6);
const int compact_gap_y = FromDIP(4);
const int compact_row_pad = FromDIP(6);
const wxColour mixed_rows_bg(246, 248, 251);
const wxColour mixed_row_bg(255, 255, 255);
p->m_panel_mixed_filaments->SetBackgroundColour(mixed_rows_bg);
p->m_sizer_mixed_filaments->Clear(true);
// Header with title and add buttons (gradient / pattern).
auto *header_row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY);
header_row->SetBackgroundColour(wxColour(255, 255, 255));
header_row->SetBackgroundColour(mixed_rows_bg);
auto *header_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *mixed_title = new wxStaticText(header_row, wxID_ANY, _L("Mixed Filaments"));
mixed_title->SetFont(Label::Head_14);
header_sizer->Add(mixed_title, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8));
header_sizer->Add(mixed_title, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
auto add_custom_row = [this, preset_bundle, physical_colors, get_mixed_mode, get_mixed_int, get_mixed_float, get_mixed_bool, set_mixed_string, notify_mixed_change](bool pattern_row) {
if (physical_colors.size() < 2)
@@ -3329,25 +3337,25 @@ void Sidebar::update_mixed_filament_panel()
};
auto *grad_label = new wxStaticText(header_row, wxID_ANY, _L("Gradient"));
header_sizer->Add(grad_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
auto *add_gradient_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)));
header_sizer->Add(grad_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
auto *add_gradient_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(22), FromDIP(20)), wxBU_EXACTFIT);
add_gradient_btn->SetToolTip(_L("Add custom gradient mixed filament"));
add_gradient_btn->Enable(num_physical >= 2);
add_gradient_btn->Bind(wxEVT_BUTTON, [add_custom_row](wxCommandEvent &) { add_custom_row(false); });
header_sizer->Add(add_gradient_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(8));
header_sizer->Add(add_gradient_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
auto *pattern_label = new wxStaticText(header_row, wxID_ANY, _L("Pattern"));
header_sizer->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
auto *add_pattern_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)));
header_sizer->Add(pattern_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
auto *add_pattern_btn = new wxButton(header_row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(22), FromDIP(20)), wxBU_EXACTFIT);
add_pattern_btn->SetToolTip(_L("Add custom pattern mixed filament"));
add_pattern_btn->Enable(num_physical >= 2);
add_pattern_btn->Bind(wxEVT_BUTTON, [add_custom_row](wxCommandEvent &) { add_custom_row(true); });
header_sizer->Add(add_pattern_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(8));
header_sizer->Add(add_pattern_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
header_row->SetSizer(header_sizer);
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
p->m_sizer_mixed_filaments->Add(header_row, 0, wxEXPAND);
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(1));
p->m_sizer_mixed_filaments->Add(header_row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(2));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(1));
if (mixed.empty()) {
p->m_panel_mixed_filaments->Hide();
@@ -3355,125 +3363,50 @@ void Sidebar::update_mixed_filament_panel()
return;
}
// Global gradient settings row.
auto *settings_row = new wxPanel(p->m_panel_mixed_filaments, wxID_ANY);
settings_row->SetBackgroundColour(wxColour(255, 255, 255));
auto *settings_sizer = new wxBoxSizer(wxHORIZONTAL);
settings_sizer->AddSpacer(FromDIP(4));
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Lower")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
auto *lower_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.01, 10.0, lower_bound, 0.01);
settings_sizer->Add(lower_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Upper")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
auto *upper_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.01, 10.0, upper_bound, 0.01);
settings_sizer->Add(upper_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Cycle")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
auto *cycle_spin = new wxSpinCtrl(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(56), -1), wxSP_ARROW_KEYS, 2, 32, cycle_layers);
settings_sizer->Add(cycle_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(3));
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Pixel")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
auto *pixel_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.0, 10.0, pointillism_pixel_size, 0.01);
pixel_spin->SetToolTip(_L("Pointillisme segment length in mm. 0 means automatic nozzle-based sizing."));
settings_sizer->Add(pixel_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
settings_sizer->Add(new wxStaticText(settings_row, wxID_ANY, _L("Gap")), 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
auto *gap_spin = new wxSpinCtrlDouble(settings_row, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS, 0.0, 10.0, pointillism_line_gap, 0.01);
gap_spin->SetToolTip(_L("Optional spacing between pointillisme line segments (mm)."));
settings_sizer->Add(gap_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(3));
settings_row->SetSizer(settings_sizer);
p->m_sizer_mixed_filaments->Add(settings_row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
auto *rows_scroller = new wxScrolledWindow(p->m_panel_mixed_filaments, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxTAB_TRAVERSAL);
rows_scroller->SetScrollRate(0, FromDIP(8));
rows_scroller->SetScrollRate(0, FromDIP(6));
rows_scroller->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT);
rows_scroller->SetBackgroundColour(wxColour(255, 255, 255));
rows_scroller->SetBackgroundColour(mixed_rows_bg);
auto *rows_sizer = new wxBoxSizer(wxVERTICAL);
rows_scroller->SetSizer(rows_sizer);
auto apply_settings = [this, preset_bundle, get_mixed_mode, get_mixed_bool, lower_spin, upper_spin, cycle_spin, pixel_spin, gap_spin,
set_mixed_int, set_mixed_float, set_mixed_string, notify_mixed_change]() {
const int mode = get_mixed_mode(false) ? 1 : 0;
const bool advanced = get_mixed_bool("mixed_filament_advanced_dithering", false);
float lo = std::max(0.01f, float(lower_spin->GetValue()));
float hi = std::max(lo, float(upper_spin->GetValue()));
int cycle = std::max(2, cycle_spin->GetValue());
float pixel = std::max(0.f, float(pixel_spin->GetValue()));
float gap = std::max(0.f, float(gap_spin->GetValue()));
if (pixel > 1e-6f)
gap = std::min(gap, pixel * 0.90f);
if (std::abs(float(upper_spin->GetValue()) - hi) > 1e-6f)
upper_spin->SetValue(hi);
if (std::abs(float(gap_spin->GetValue()) - gap) > 1e-6f)
gap_spin->SetValue(gap);
set_mixed_float("mixed_filament_height_lower_bound", lo);
set_mixed_float("mixed_filament_height_upper_bound", hi);
set_mixed_int("mixed_filament_cycle_layers", cycle);
set_mixed_float("mixed_filament_pointillism_pixel_size", pixel);
set_mixed_float("mixed_filament_pointillism_line_gap", gap);
auto &mgr = preset_bundle->mixed_filaments;
mgr.apply_gradient_settings(mode, lo, hi, cycle, advanced);
set_mixed_string("mixed_filament_definitions", mgr.serialize_custom_entries());
notify_mixed_change();
this->CallAfter([this]() {
update_dynamic_filament_list();
update_mixed_filament_panel();
});
};
lower_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
upper_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
cycle_spin->Bind(wxEVT_SPINCTRL, [apply_settings](wxSpinEvent &) { apply_settings(); });
pixel_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
gap_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_settings](wxSpinDoubleEvent &) { apply_settings(); });
lower_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
upper_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
cycle_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
pixel_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
gap_spin->Bind(wxEVT_TEXT, [apply_settings](wxCommandEvent &) { apply_settings(); });
lower_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
upper_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
cycle_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
pixel_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
gap_spin->Bind(wxEVT_TEXT_ENTER, [apply_settings](wxCommandEvent &) { apply_settings(); });
lower_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
upper_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
cycle_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
pixel_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
gap_spin->Bind(wxEVT_KILL_FOCUS, [apply_settings](wxFocusEvent &evt) { apply_settings(); evt.Skip(); });
for (size_t mixed_id = 0; mixed_id < mixed.size(); ++mixed_id) {
MixedFilament &mf = mixed[mixed_id];
auto *row = new wxPanel(rows_scroller, wxID_ANY);
row->SetBackgroundColour(wxColour(255, 255, 255));
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
const bool pattern_row_mode = !normalized_pattern.empty();
auto *row = new wxPanel(rows_scroller, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE);
row->SetBackgroundColour(mixed_row_bg);
auto *row_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *content_sizer = new wxBoxSizer(wxVERTICAL);
auto *title_row = new wxBoxSizer(wxHORIZONTAL);
wxColour swatch_color(mf.display_color);
auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(14), FromDIP(14)));
auto *swatch = new wxPanel(row, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(12), FromDIP(12)));
swatch->SetBackgroundColour(swatch_color);
swatch->SetMinSize(wxSize(FromDIP(14), FromDIP(14)));
title_row->Add(swatch, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(4));
swatch->SetMinSize(wxSize(FromDIP(12), FromDIP(12)));
title_row->Add(swatch, 0, wxALIGN_CENTER_VERTICAL);
const int virtual_filament_id = int(num_physical + mixed_id + 1);
auto *name_label = new wxStaticText(row, wxID_ANY, wxString::Format("Mixed Filament %d", virtual_filament_id));
title_row->Add(name_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
title_row->Add(name_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
if (!mf.custom) {
auto *pair_label = new wxStaticText(row, wxID_ANY, wxString::Format("(Filament %u + Filament %u)",
unsigned(mf.component_a), unsigned(mf.component_b)));
title_row->Add(pair_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
pair_label->SetForegroundColour(wxColour(96, 96, 96));
title_row->Add(pair_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
} else {
auto *pair_label = new wxStaticText(row, wxID_ANY, wxString::Format("F%u + F%u",
unsigned(mf.component_a), unsigned(mf.component_b)));
pair_label->SetForegroundColour(wxColour(96, 96, 96));
title_row->Add(pair_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
title_row->AddStretchSpacer(1);
auto *kind_label = new wxStaticText(row, wxID_ANY, pattern_row_mode ? _L("Pattern") : _L("Gradient"));
kind_label->SetForegroundColour(wxColour(84, 84, 84));
title_row->Add(kind_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
}
content_sizer->Add(title_row, 0, wxEXPAND);
@@ -3494,10 +3427,8 @@ void Sidebar::update_mixed_filament_panel()
picker_row->Add(choice_a, 1, wxALIGN_CENTER_VERTICAL);
picker_row->Add(new wxStaticText(row, wxID_ANY, "+"), 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(6));
picker_row->Add(choice_b, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(picker_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(picker_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
const std::string normalized_pattern = MixedFilamentManager::normalize_manual_pattern(mf.manual_pattern);
const bool pattern_row_mode = !normalized_pattern.empty();
MixedGradientSelector *blend_selector = nullptr;
wxStaticText *blend_label = nullptr;
wxTextCtrl *pattern_ctrl = nullptr;
@@ -3523,20 +3454,21 @@ void Sidebar::update_mixed_filament_panel()
distribution_row->Add(new wxStaticText(row, wxID_ANY, _L("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
distribution_choice = new wxChoice(row, wxID_ANY, wxDefaultPosition, wxDefaultSize, distribution_choices);
distribution_choice->SetSelection(row_distribution_mode);
distribution_choice->SetToolTip(_L("Choose whether this mixed row alternates by layer or interleaves colors on the same layer."));
distribution_choice->SetToolTip(_L("Choose whether this mixed row alternates by layer or interleaves colors on the same layer.\n\n"
"Warning: Same-layer pointillisme is extremely experimental and may produce unusable results."));
distribution_row->Add(distribution_choice, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(distribution_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(distribution_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
if (pattern_row_mode) {
auto *pattern_row = new wxBoxSizer(wxHORIZONTAL);
pattern_row->Add(new wxStaticText(row, wxID_ANY, _L("Pattern")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
pattern_ctrl = new wxTextCtrl(row, wxID_ANY, from_u8(normalized_pattern), wxDefaultPosition,
wxSize(FromDIP(170), -1), wxTE_PROCESS_ENTER);
wxSize(FromDIP(152), -1), wxTE_PROCESS_ENTER);
pattern_ctrl->SetToolTip(_L("Manual repeating pattern. Use 1/2 or A/B for component A/B, "
"and 3..9 for direct physical filament IDs. "
"Example: 1/1/1/1/2/2/2/2 or 1/2/3/4."));
pattern_row->Add(pattern_ctrl, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(pattern_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
auto *insert_row = new wxBoxSizer(wxHORIZONTAL);
insert_row->Add(new wxStaticText(row, wxID_ANY, _L("Insert")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
@@ -3544,10 +3476,10 @@ void Sidebar::update_mixed_filament_panel()
pattern_insert_choice->SetSelection(component_a - 1);
pattern_insert_choice->SetToolTip(_L("Select a physical filament to append into the pattern."));
insert_row->Add(pattern_insert_choice, 1, wxALIGN_CENTER_VERTICAL);
pattern_insert_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(28), FromDIP(24)), wxBU_EXACTFIT);
pattern_insert_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)), wxBU_EXACTFIT);
pattern_insert_btn->SetToolTip(_L("Append selected filament ID to pattern"));
insert_row->Add(pattern_insert_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
content_sizer->Add(insert_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(insert_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
auto *quick_row = new wxBoxSizer(wxHORIZONTAL);
quick_row->Add(new wxStaticText(row, wxID_ANY, _L("Filaments")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
@@ -3562,7 +3494,7 @@ void Sidebar::update_mixed_filament_panel()
pattern_quick_filament_buttons.emplace_back(btn);
}
quick_row->Add(quick_buttons, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(quick_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(quick_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
} else {
wxArrayString optional_filament_choices;
optional_filament_choices.Add(_L("None"));
@@ -3594,7 +3526,7 @@ void Sidebar::update_mixed_filament_panel()
blend_selector = new MixedGradientSelector(row, color_a, color_b, std::clamp(mf.mix_b_percent, 0, 100));
auto *blend_row = new wxBoxSizer(wxHORIZONTAL);
blend_row->Add(blend_selector, 1, wxEXPAND);
content_sizer->Add(blend_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(blend_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
const bool same_layer_mode = row_distribution_mode == int(MixedFilament::SameLayerPointillisme);
blend_label = new wxStaticText(row, wxID_ANY, multi_gradient_mode ?
@@ -3607,7 +3539,7 @@ void Sidebar::update_mixed_filament_panel()
auto *ratio_row = new wxBoxSizer(wxHORIZONTAL);
ratio_row->AddStretchSpacer(1);
ratio_row->Add(blend_label, 0, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(ratio_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(ratio_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
if (num_physical >= 3 && !simple_mode) {
add_extra_color_btn = new wxButton(row, wxID_ANY, "+", wxDefaultPosition, wxSize(FromDIP(24), FromDIP(22)), wxBU_EXACTFIT);
@@ -3624,7 +3556,7 @@ void Sidebar::update_mixed_filament_panel()
extra_row->Add(choice_c, 1, wxALIGN_CENTER_VERTICAL);
extra_row->Add(new wxStaticText(row, wxID_ANY, "+"), 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, FromDIP(6));
extra_row->Add(choice_d, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(extra_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
content_sizer->Add(extra_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
}
if (blend_selector) {
@@ -3640,14 +3572,14 @@ void Sidebar::update_mixed_filament_panel()
}
auto *preview_row = new wxBoxSizer(wxHORIZONTAL);
preview_row->Add(new wxStaticText(row, wxID_ANY, _L("Preview")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
preview_row->Add(new wxStaticText(row, wxID_ANY, _L("Preview")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap_x);
mix_preview = new MixedMixPreview(row);
preview_row->Add(mix_preview, 1, wxALIGN_CENTER_VERTICAL);
content_sizer->Add(preview_row, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
mix_summary_label = new wxStaticText(row, wxID_ANY, wxEmptyString);
mix_summary_label->SetForegroundColour(wxColour(90, 90, 90));
content_sizer->Add(mix_summary_label, 0, wxEXPAND | wxLEFT | wxTOP, FromDIP(8));
preview_row->Add(mix_summary_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, compact_gap_x);
content_sizer->Add(preview_row, 0, wxEXPAND | wxLEFT | wxTOP, compact_gap_y);
{
std::vector<unsigned int> initial_sequence;
@@ -3978,11 +3910,11 @@ void Sidebar::update_mixed_filament_panel()
}
}
row_sizer->Add(content_sizer, 1, wxEXPAND);
row_sizer->Add(content_sizer, 1, wxEXPAND | wxALL, compact_row_pad);
auto *chk = new wxCheckBox(row, wxID_ANY, wxEmptyString);
chk->SetValue(mf.enabled);
row_sizer->Add(chk, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP, FromDIP(2));
row_sizer->Add(chk, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_row_pad);
chk->Bind(wxEVT_CHECKBOX, [this, preset_bundle, mixed_id, chk, set_mixed_string, notify_mixed_change](wxCommandEvent &) {
auto &mgr = preset_bundle->mixed_filaments;
auto &mfs = mgr.mixed_filaments();
@@ -3999,16 +3931,16 @@ void Sidebar::update_mixed_filament_panel()
});
row->SetSizer(row_sizer);
rows_sizer->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
rows_sizer->AddSpacer(FromDIP(1));
rows_sizer->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(2));
rows_sizer->AddSpacer(FromDIP(2));
}
rows_sizer->AddSpacer(FromDIP(4));
rows_sizer->AddSpacer(FromDIP(2));
rows_scroller->Layout();
rows_scroller->FitInside();
const int min_h = FromDIP(72);
const int max_h = FromDIP(260);
const int min_h = FromDIP(68);
const int max_h = FromDIP(220);
const int content_h = std::max(0, rows_scroller->GetVirtualSize().GetHeight());
const int desired_h = std::clamp(content_h, min_h, max_h);
rows_scroller->SetMinSize(wxSize(-1, desired_h));
@@ -4016,8 +3948,8 @@ void Sidebar::update_mixed_filament_panel()
if (prev_rows_view_y > 0)
rows_scroller->Scroll(0, prev_rows_view_y);
p->m_sizer_mixed_filaments->Add(rows_scroller, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(0));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(4));
p->m_sizer_mixed_filaments->Add(rows_scroller, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(2));
p->m_sizer_mixed_filaments->AddSpacer(FromDIP(2));
p->m_panel_mixed_filaments->Show();
p->m_panel_mixed_filaments->Layout();
Layout();
+2
View File
@@ -1791,6 +1791,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
opt_key == "mixed_filament_advanced_dithering" ||
opt_key == "mixed_filament_pointillism_pixel_size" ||
opt_key == "mixed_filament_pointillism_line_gap" ||
opt_key == "mixed_filament_surface_indentation" ||
opt_key == "dithering_z_step_size" ||
opt_key == "dithering_local_z_mode" ||
opt_key == "dithering_step_painted_zones_only" ||
@@ -2522,6 +2523,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
optgroup->append_single_option_line("mixed_filament_advanced_dithering");
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("dithering_z_step_size");
optgroup->append_single_option_line("dithering_local_z_mode");
optgroup->append_single_option_line("dithering_step_painted_zones_only");