Add mixed filament bias controls and bump version to 0.9.51

This commit is contained in:
Rad
2026-04-13 21:00:30 +02:00
parent f5ce77c15a
commit 4a5746a6e6
27 changed files with 530 additions and 159 deletions
+3
View File
@@ -11,6 +11,7 @@ on:
- 'version.inc' - 'version.inc'
- 'CHANGELOG*.md' - 'CHANGELOG*.md'
- 'RELEASE_NOTES*.md' - 'RELEASE_NOTES*.md'
- 'doc/changelogs/**'
- 'localization/**' - 'localization/**'
- 'resources/**' - 'resources/**'
- ".github/workflows/build_*.yml" - ".github/workflows/build_*.yml"
@@ -76,6 +77,8 @@ jobs:
notes_content="" notes_content=""
for notes_file in \ for notes_file in \
"doc/changelogs/CHANGELOG_v${version}.md" \
"doc/changelogs/RELEASE_NOTES_v${version}.md" \
"CHANGELOG_v${version}.md" \ "CHANGELOG_v${version}.md" \
"RELEASE_NOTES_v${version}.md"; do "RELEASE_NOTES_v${version}.md"; do
if [ -f "$notes_file" ]; then if [ -f "$notes_file" ]; then
+11
View File
@@ -48,6 +48,7 @@ Snapmaker Orca FullSpectrum includes support for **virtual mixed-color filaments
- Visual preview showing the additive color blend - Visual preview showing the additive color blend
- Enable/disable individual mixed filaments - Enable/disable individual mixed filaments
- Per-layer resolution control with customizable ratios - Per-layer resolution control with customizable ratios
- Optional per-pair Bias control for slightly recessing one component to push the apparent color toward the other
- Seamless integration with the existing filament management system - Seamless integration with the existing filament management system
### Using Mixed Filaments ### Using Mixed Filaments
@@ -61,6 +62,16 @@ Snapmaker Orca FullSpectrum includes support for **virtual mixed-color filaments
4. Mixed filaments can be assigned to objects just like physical filaments 4. Mixed filaments can be assigned to objects just like physical filaments
5. During slicing, the mixed filament resolves to alternating layers of its components 5. During slicing, the mixed filament resolves to alternating layers of its components
### Bias Control
Snapmaker Orca FullSpectrum also includes an optional **Bias** control for mixed filament pairs. When enabled in **Print Settings -> Others -> Mixed Filaments**, each mixed row gets a compact inline Bias value:
- **Positive Bias** recesses the second filament in the pair
- **Negative Bias** recesses the first filament in the pair
- This lets you shift the apparent color without changing the nominal layer cadence
- The inline preview updates to show the estimated apparent mix shift
Example: for a pair like `F1 + F2`, a positive bias makes `F2` sit slightly lower, so `F1` visually dominates more. A negative bias does the opposite and recesses `F1`.
### Dithering Settings ### Dithering Settings
Snapmaker Orca FullSpectrum includes advanced dithering controls to fine-tune the layer alternation behavior for mixed filaments. These settings are found in **Others → Dithering** in the print settings: Snapmaker Orca FullSpectrum includes advanced dithering controls to fine-tune the layer alternation behavior for mixed filaments. These settings are found in **Others → Dithering** in the print settings:
+5 -5
View File
@@ -1308,10 +1308,9 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
bool has_delimiters = true; bool has_delimiters = true;
{ {
//BBS //BBS
std::string bambuslicer_gcode_header = "; Snapmaker_Orca"; std::string bambuslicer_gcode_header = "; Snapmaker_Orca";
std::string legacy_fs_gcode_header = std::string("; generated by ") + SLIC3R_APP_NAME;
std::string Snapmaker_Orca_gcode_header = std::string("; generated by "); std::string compat_snapmaker_gcode_header = "; generated by Snapmaker Orca";
Snapmaker_Orca_gcode_header += SLIC3R_APP_NAME;
std::string header; std::string header;
bool header_found = false; bool header_found = false;
@@ -1323,7 +1322,8 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
line_c = skip_whitespaces(line_c); line_c = skip_whitespaces(line_c);
// BBS // BBS
if (strncmp(bambuslicer_gcode_header.c_str(), line_c, strlen(bambuslicer_gcode_header.c_str())) == 0 || if (strncmp(bambuslicer_gcode_header.c_str(), line_c, strlen(bambuslicer_gcode_header.c_str())) == 0 ||
strncmp(Snapmaker_Orca_gcode_header.c_str(), line_c, strlen(Snapmaker_Orca_gcode_header.c_str())) == 0) { strncmp(legacy_fs_gcode_header.c_str(), line_c, strlen(legacy_fs_gcode_header.c_str())) == 0 ||
strncmp(compat_snapmaker_gcode_header.c_str(), line_c, strlen(compat_snapmaker_gcode_header.c_str())) == 0) {
header_found = true; header_found = true;
break; break;
} }
+1
View File
@@ -619,6 +619,7 @@ const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProces
//BBS: Snapmaker_Orca is also "bambu". Otherwise the time estimation didn't work. //BBS: Snapmaker_Orca is also "bambu". Otherwise the time estimation didn't work.
//FIXME: Workaround and should be handled when do removing-bambu //FIXME: Workaround and should be handled when do removing-bambu
{ EProducer::Snapmaker_Orca, SLIC3R_APP_NAME }, { EProducer::Snapmaker_Orca, SLIC3R_APP_NAME },
{ EProducer::Snapmaker_Orca, "generated by Snapmaker Orca" },
{ EProducer::Snapmaker_Orca, "generated by Snapmaker_Orca" }, { EProducer::Snapmaker_Orca, "generated by Snapmaker_Orca" },
{ EProducer::Snapmaker_Orca, "generated by BambuStudio" }, { EProducer::Snapmaker_Orca, "generated by BambuStudio" },
{ EProducer::Snapmaker_Orca, "BambuStudio" } { EProducer::Snapmaker_Orca, "BambuStudio" }
+53 -6
View File
@@ -216,6 +216,18 @@ static float clamp_surface_offset(float v)
return std::clamp(v, -2.f, 2.f); return std::clamp(v, -2.f, 2.f);
} }
static float canonical_signed_bias_value(float component_a_surface_offset, float component_b_surface_offset)
{
const float offset_a = clamp_surface_offset(component_a_surface_offset);
const float offset_b = clamp_surface_offset(component_b_surface_offset);
if (std::abs(offset_b) > EPSILON)
return offset_b;
if (std::abs(offset_a) > EPSILON)
return (offset_a >= 0.f) ? -std::abs(offset_a) : std::abs(offset_a);
return 0.f;
}
static std::string format_surface_offset_token(float value) static std::string format_surface_offset_token(float value)
{ {
std::ostringstream ss; std::ostringstream ss;
@@ -1444,10 +1456,11 @@ float MixedFilamentManager::component_surface_offset(unsigned int filament_id,
layer_print_z, layer_print_z,
layer_height, layer_height,
force_height_weighted); force_height_weighted);
if (resolved == mixed_row->component_a) const float signed_bias = canonical_signed_bias_value(mixed_row->component_a_surface_offset, mixed_row->component_b_surface_offset);
return clamp_surface_offset(mixed_row->component_a_surface_offset); if (signed_bias > EPSILON && resolved == mixed_row->component_b)
if (resolved == mixed_row->component_b) return signed_bias;
return clamp_surface_offset(mixed_row->component_b_surface_offset); if (signed_bias < -EPSILON && resolved == mixed_row->component_a)
return -signed_bias;
return 0.f; return 0.f;
} }
@@ -1591,14 +1604,48 @@ std::string MixedFilamentManager::blend_color(const std::string &color_a,
return rgb_to_hex({int(out_r), int(out_g), int(out_b)}); return rgb_to_hex({int(out_r), int(out_g), int(out_b)});
} }
float MixedFilamentManager::max_component_surface_offset_mm(float reference_width_mm)
{
const float safe_reference = std::max(0.05f, std::abs(reference_width_mm));
return std::clamp(safe_reference, 0.01f, 0.35f);
}
float MixedFilamentManager::max_pair_bias_mm(float reference_width_mm)
{
return max_component_surface_offset_mm(reference_width_mm);
}
std::pair<float, float> MixedFilamentManager::surface_offset_pair_from_signed_bias(float bias_mm,
float reference_width_mm)
{
const float clamped_bias = std::clamp(bias_mm,
-max_pair_bias_mm(reference_width_mm),
max_pair_bias_mm(reference_width_mm));
if (clamped_bias > EPSILON)
return std::make_pair(0.f, clamped_bias);
if (clamped_bias < -EPSILON)
return std::make_pair(-clamped_bias, 0.f);
return std::make_pair(0.f, 0.f);
}
float MixedFilamentManager::bias_ui_value_from_surface_offsets(float component_a_surface_offset,
float component_b_surface_offset,
float reference_width_mm)
{
return std::clamp(canonical_signed_bias_value(component_a_surface_offset, component_b_surface_offset),
-max_pair_bias_mm(reference_width_mm),
max_pair_bias_mm(reference_width_mm));
}
int MixedFilamentManager::apparent_mix_b_percent(int mix_b_percent, int MixedFilamentManager::apparent_mix_b_percent(int mix_b_percent,
float component_a_surface_offset, float component_a_surface_offset,
float component_b_surface_offset, float component_b_surface_offset,
float reference_width_mm) float reference_width_mm)
{ {
const float safe_reference = std::max(0.05f, std::abs(reference_width_mm)); const float safe_reference = std::max(0.05f, std::abs(reference_width_mm));
const float shift_pct = 100.f * (clamp_surface_offset(component_a_surface_offset) - const float shift_pct = -100.f * std::clamp(canonical_signed_bias_value(component_a_surface_offset, component_b_surface_offset),
clamp_surface_offset(component_b_surface_offset)) / safe_reference; -max_pair_bias_mm(reference_width_mm),
max_pair_bias_mm(reference_width_mm)) / safe_reference;
return clamp_int(int(std::lround(float(clamp_int(mix_b_percent, 0, 100)) + shift_pct)), 0, 100); return clamp_int(int(std::lround(float(clamp_int(mix_b_percent, 0, 100)) + shift_pct)), 0, 100);
} }
+7
View File
@@ -229,6 +229,13 @@ public:
static std::string blend_color(const std::string &color_a, static std::string blend_color(const std::string &color_a,
const std::string &color_b, const std::string &color_b,
int ratio_a, int ratio_b); int ratio_a, int ratio_b);
static float max_component_surface_offset_mm(float reference_width_mm = 0.4f);
static float max_pair_bias_mm(float reference_width_mm = 0.4f);
static std::pair<float, float> surface_offset_pair_from_signed_bias(float bias_mm,
float reference_width_mm = 0.4f);
static float bias_ui_value_from_surface_offsets(float component_a_surface_offset,
float component_b_surface_offset,
float reference_width_mm = 0.4f);
static int apparent_mix_b_percent(int mix_b_percent, static int apparent_mix_b_percent(int mix_b_percent,
float component_a_surface_offset, float component_a_surface_offset,
float component_b_surface_offset, float component_b_surface_offset,
+1
View File
@@ -74,6 +74,7 @@ static std::vector<std::string> s_project_options {
"mixed_filament_height_lower_bound", "mixed_filament_height_lower_bound",
"mixed_filament_height_upper_bound", "mixed_filament_height_upper_bound",
"mixed_filament_advanced_dithering", "mixed_filament_advanced_dithering",
"mixed_filament_component_bias_enabled",
"mixed_filament_surface_indentation", "mixed_filament_surface_indentation",
"mixed_filament_region_collapse", "mixed_filament_region_collapse",
"mixed_filament_definitions", "mixed_filament_definitions",
+1
View File
@@ -296,6 +296,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "mixed_filament_height_lower_bound" || opt_key == "mixed_filament_height_lower_bound"
|| opt_key == "mixed_filament_height_upper_bound" || opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_advanced_dithering" || opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_component_bias_enabled"
|| opt_key == "mixed_filament_surface_indentation" || opt_key == "mixed_filament_surface_indentation"
|| opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_region_collapse"
|| opt_key == "mixed_filament_definitions" || opt_key == "mixed_filament_definitions"
+3
View File
@@ -1208,6 +1208,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_advanced_dithering", true);
new_full_config.option("mixed_filament_pointillism_pixel_size", 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_pointillism_line_gap", true);
new_full_config.option("mixed_filament_component_bias_enabled", true);
new_full_config.option("mixed_filament_surface_indentation", 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_region_collapse", true);
new_full_config.option("mixed_filament_definitions", true); new_full_config.option("mixed_filament_definitions", true);
@@ -1220,6 +1221,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_config.option("mixed_filament_advanced_dithering", true); m_config.option("mixed_filament_advanced_dithering", true);
m_config.option("mixed_filament_pointillism_pixel_size", true); m_config.option("mixed_filament_pointillism_pixel_size", true);
m_config.option("mixed_filament_pointillism_line_gap", true); m_config.option("mixed_filament_pointillism_line_gap", true);
m_config.option("mixed_filament_component_bias_enabled", true);
m_config.option("mixed_filament_surface_indentation", true); m_config.option("mixed_filament_surface_indentation", true);
m_config.option("mixed_filament_region_collapse", true); m_config.option("mixed_filament_region_collapse", true);
m_config.option("mixed_filament_definitions", true); m_config.option("mixed_filament_definitions", true);
@@ -1232,6 +1234,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_advanced_dithering", true);
m_default_object_config.option("mixed_filament_pointillism_pixel_size", 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_pointillism_line_gap", true);
m_default_object_config.option("mixed_filament_component_bias_enabled", true);
m_default_object_config.option("mixed_filament_surface_indentation", 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_region_collapse", true);
m_default_object_config.option("mixed_filament_definitions", true); m_default_object_config.option("mixed_filament_definitions", true);
+9
View File
@@ -4232,6 +4232,15 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0)); def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("mixed_filament_component_bias_enabled", coBool);
def->label = L("Enable mixed filament bias");
def->category = L("Others");
def->tooltip = L("Show and apply the per-row mixed filament Bias control.\n\n"
"When enabled, the selected filament in a mixed pair is recessed slightly so the other component becomes more visible.\n\n"
"Bias is ignored for grouped wall patterns, same-layer pointillisme, and Local Z dithering.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("mixed_filament_surface_indentation", coFloat); def = this->add("mixed_filament_surface_indentation", coFloat);
def->label = L("Selective Expansion contraction"); def->label = L("Selective Expansion contraction");
def->category = L("Others"); def->category = L("Others");
+1
View File
@@ -1362,6 +1362,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, mixed_filament_advanced_dithering)) ((ConfigOptionBool, mixed_filament_advanced_dithering))
((ConfigOptionFloat, mixed_filament_pointillism_pixel_size)) ((ConfigOptionFloat, mixed_filament_pointillism_pixel_size))
((ConfigOptionFloat, mixed_filament_pointillism_line_gap)) ((ConfigOptionFloat, mixed_filament_pointillism_line_gap))
((ConfigOptionBool, mixed_filament_component_bias_enabled))
((ConfigOptionFloat, mixed_filament_surface_indentation)) ((ConfigOptionFloat, mixed_filament_surface_indentation))
((ConfigOptionBool, mixed_filament_region_collapse)) ((ConfigOptionBool, mixed_filament_region_collapse))
((ConfigOptionString, mixed_filament_definitions)) ((ConfigOptionString, mixed_filament_definitions))
+2
View File
@@ -950,6 +950,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "dithering_z_step_size" || opt_key == "dithering_z_step_size"
|| opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_mode"
|| opt_key == "dithering_step_painted_zones_only" || opt_key == "dithering_step_painted_zones_only"
|| opt_key == "mixed_filament_component_bias_enabled"
|| opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_region_collapse"
|| opt_key == "mmu_segmented_region_max_width" || opt_key == "mmu_segmented_region_max_width"
|| opt_key == "mmu_segmented_region_interlocking_depth" || opt_key == "mmu_segmented_region_interlocking_depth"
@@ -973,6 +974,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "mixed_filament_height_lower_bound" || opt_key == "mixed_filament_height_lower_bound"
|| opt_key == "mixed_filament_height_upper_bound" || opt_key == "mixed_filament_height_upper_bound"
|| opt_key == "mixed_filament_advanced_dithering" || opt_key == "mixed_filament_advanced_dithering"
|| opt_key == "mixed_filament_component_bias_enabled"
|| opt_key == "mixed_filament_surface_indentation" || opt_key == "mixed_filament_surface_indentation"
|| opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_region_collapse"
|| opt_key == "mixed_filament_definitions") { || opt_key == "mixed_filament_definitions") {
+110 -18
View File
@@ -897,6 +897,52 @@ static inline unsigned int segmentation_channel_filament_id(size_t channel_idx)
return unsigned(channel_idx); return unsigned(channel_idx);
} }
static float mixed_filament_reference_nozzle_mm(const MixedFilament &mixed_row, const ConfigOptionFloats &nozzle_diameters)
{
std::vector<float> samples;
samples.reserve(2);
auto append_if_valid = [&samples, &nozzle_diameters](unsigned int component_id) {
if (component_id >= 1 && component_id <= nozzle_diameters.size())
samples.emplace_back(std::max(0.05f, float(nozzle_diameters.get_at(component_id - 1))));
};
append_if_valid(mixed_row.component_a);
append_if_valid(mixed_row.component_b);
if (samples.empty())
return 0.4f;
return std::accumulate(samples.begin(), samples.end(), 0.0f) / float(samples.size());
}
static coordf_t clamped_mixed_component_surface_offset(const MixedFilamentManager &mixed_mgr,
const PrintConfig &print_cfg,
unsigned int filament_id,
size_t num_physical,
int layer_index,
float layer_print_z,
float layer_height,
bool force_height_weighted = false)
{
const MixedFilament *mixed_row = mixed_mgr.mixed_filament_from_id(filament_id, num_physical);
if (mixed_row == nullptr)
return 0.f;
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(
filament_id,
num_physical,
layer_index,
layer_print_z,
layer_height,
force_height_weighted));
if (std::abs(offset_mm) <= EPSILON)
return 0.f;
const float reference_nozzle = mixed_filament_reference_nozzle_mm(*mixed_row, print_cfg.nozzle_diameter);
const coordf_t limit_mm = coordf_t(MixedFilamentManager::max_component_surface_offset_mm(reference_nozzle));
return std::clamp(offset_mm, -limit_mm, limit_mm);
}
static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation) static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
{ {
const Print *print = print_object.print(); const Print *print = print_object.print();
@@ -1041,6 +1087,8 @@ static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std
const DynamicPrintConfig &full_cfg = print->full_print_config(); const DynamicPrintConfig &full_cfg = print->full_print_config();
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value)) if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
return false; return false;
if (!bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value))
return false;
const size_t num_physical = print_cfg.filament_colour.size(); const size_t num_physical = print_cfg.filament_colour.size();
const size_t num_channels = segmentation.front().size(); const size_t num_channels = segmentation.front().size();
@@ -1065,7 +1113,6 @@ static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std
size_t emptied_states = 0; size_t emptied_states = 0;
size_t expanded_states = 0; size_t expanded_states = 0;
size_t contracted_states = 0; size_t contracted_states = 0;
size_t overlap_clipped_states = 0;
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) { for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
if (segmentation[layer_id].size() != num_channels) if (segmentation[layer_id].size() != num_channels)
@@ -1085,11 +1132,13 @@ static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std
if (!mixed_mgr.is_mixed(state_id, num_physical)) if (!mixed_mgr.is_mixed(state_id, num_physical))
continue; continue;
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(state_id, const coordf_t offset_mm = clamped_mixed_component_surface_offset(mixed_mgr,
num_physical, print_cfg,
int(layer_id), state_id,
layer_print_z, num_physical,
layer_height)); int(layer_id),
layer_print_z,
layer_height);
if (std::abs(offset_mm) <= EPSILON) if (std::abs(offset_mm) <= EPSILON)
continue; continue;
@@ -1113,8 +1162,6 @@ static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std
occupied_other = union_ex(occupied_other); occupied_other = union_ex(occupied_other);
if (!occupied_other.empty()) { if (!occupied_other.empty()) {
ExPolygons clipped = diff_ex(adjusted, occupied_other, ApplySafetyOffset::Yes); ExPolygons clipped = diff_ex(adjusted, occupied_other, ApplySafetyOffset::Yes);
if (std::abs(area(clipped)) + EPSILON < std::abs(area(adjusted)))
++overlap_clipped_states;
adjusted = std::move(clipped); adjusted = std::move(clipped);
if (!adjusted.empty() && adjusted.size() > 1) if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted); adjusted = union_ex(adjusted);
@@ -1145,8 +1192,7 @@ static bool apply_mixed_component_surface_offsets(PrintObject &print_object, std
<< " changed_states=" << changed_states << " changed_states=" << changed_states
<< " contracted_states=" << contracted_states << " contracted_states=" << contracted_states
<< " expanded_states=" << expanded_states << " expanded_states=" << expanded_states
<< " emptied_states=" << emptied_states << " emptied_states=" << emptied_states;
<< " overlap_clipped_states=" << overlap_clipped_states;
return true; return true;
} }
@@ -2311,6 +2357,8 @@ static bool apply_mixed_region_surface_offsets(PrintObject &print_object)
const DynamicPrintConfig &full_cfg = print->full_print_config(); const DynamicPrintConfig &full_cfg = print->full_print_config();
if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value)) if (bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value))
return false; return false;
if (!bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value))
return false;
const size_t num_physical = print_cfg.filament_diameter.size(); const size_t num_physical = print_cfg.filament_diameter.size();
if (num_physical == 0) if (num_physical == 0)
@@ -2355,11 +2403,13 @@ static bool apply_mixed_region_surface_offsets(PrintObject &print_object)
if (!mixed_mgr.is_mixed(filament_id, num_physical)) if (!mixed_mgr.is_mixed(filament_id, num_physical))
continue; continue;
const coordf_t offset_mm = coordf_t(mixed_mgr.component_surface_offset(filament_id, const coordf_t offset_mm = clamped_mixed_component_surface_offset(mixed_mgr,
num_physical, print_cfg,
int(layer_id), filament_id,
float(layer.print_z), num_physical,
float(layer.height))); int(layer_id),
float(layer.print_z),
float(layer.height));
if (std::abs(offset_mm) <= EPSILON) if (std::abs(offset_mm) <= EPSILON)
continue; continue;
@@ -2367,7 +2417,8 @@ static bool apply_mixed_region_surface_offsets(PrintObject &print_object)
if (delta_scaled <= float(EPSILON)) if (delta_scaled <= float(EPSILON))
continue; continue;
ExPolygons adjusted = offset_ex(to_expolygons(layerm->slices.surfaces), offset_mm > 0 ? -delta_scaled : delta_scaled); const ExPolygons original = to_expolygons(layerm->slices.surfaces);
ExPolygons adjusted = offset_ex(original, offset_mm > 0 ? -delta_scaled : delta_scaled);
if (!adjusted.empty() && adjusted.size() > 1) if (!adjusted.empty() && adjusted.size() > 1)
adjusted = union_ex(adjusted); adjusted = union_ex(adjusted);
@@ -3255,11 +3306,13 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
const coordf_t base_height = std::max<coordf_t>(0.01f, coordf_t(print_object.config().layer_height.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 = const bool collapse_mixed_regions =
bool_from_full_config(full_cfg, "mixed_filament_region_collapse", print_cfg.mixed_filament_region_collapse.value); bool_from_full_config(full_cfg, "mixed_filament_region_collapse", print_cfg.mixed_filament_region_collapse.value);
const bool bias_mode_enabled =
bool_from_full_config(full_cfg, "mixed_filament_component_bias_enabled", print_cfg.mixed_filament_component_bias_enabled.value);
const MixedFilamentManager &mixed_mgr = print_object.print()->mixed_filament_manager(); const MixedFilamentManager &mixed_mgr = print_object.print()->mixed_filament_manager();
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
[&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) { [&print_object, &segmentation, &mixed_mgr, num_physical, preferred_a, preferred_b, base_height, collapse_mixed_regions, bias_mode_enabled, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges; const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
double z = print_object.get_layer(int(range.begin()))->slice_z; double z = print_object.get_layer(int(range.begin()))->slice_z;
auto it_layer_range = layer_range_first(layer_ranges, z); auto it_layer_range = layer_range_first(layer_ranges, z);
@@ -3334,6 +3387,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
std::vector<int> missing_target_extruders; std::vector<int> missing_target_extruders;
ExPolygons default_segmentation = num_channels > 0 ? std::move(segmentation[layer_id][0]) : ExPolygons(); ExPolygons default_segmentation = num_channels > 0 ? std::move(segmentation[layer_id][0]) : ExPolygons();
BoundingBox default_bbox; BoundingBox default_bbox;
bool layer_has_component_bias = false;
if (!default_segmentation.empty()) { if (!default_segmentation.empty()) {
default_bbox = get_extents(default_segmentation); default_bbox = get_extents(default_segmentation);
layer_split = true; layer_split = true;
@@ -3358,11 +3412,37 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
region.bbox = get_extents(region.expolygons); region.bbox = get_extents(region.expolygons);
layer_split = true; layer_split = true;
} }
if (!region.expolygons.empty() &&
bias_mode_enabled &&
mixed_mgr.is_mixed(channel_id, num_physical) &&
std::abs(mixed_mgr.component_surface_offset(channel_id,
num_physical,
int(layer_id),
float(layer.print_z),
float(layer.height))) > EPSILON)
layer_has_component_bias = true;
} }
if (!layer_split) if (!layer_split)
continue; continue;
ExPolygons layer_geometry_mask;
BoundingBox layer_geometry_bbox;
if (layer_has_component_bias) {
if (!default_segmentation.empty())
append(layer_geometry_mask, default_segmentation);
for (const ByExtruder &segmented : by_extruder) {
if (!segmented.expolygons.empty())
append(layer_geometry_mask, segmented.expolygons);
}
if (!layer_geometry_mask.empty()) {
if (layer_geometry_mask.size() > 1)
layer_geometry_mask = closing_ex(union_ex(std::move(layer_geometry_mask)), scaled<float>(5. * EPSILON));
layer_geometry_bbox = get_extents(layer_geometry_mask);
}
}
// Split LayerRegions by by_extruder regions. // Split LayerRegions by by_extruder regions.
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
auto it_painted_region_begin = layer_range.painted_regions.cbegin(); auto it_painted_region_begin = layer_range.painted_regions.cbegin();
@@ -3399,6 +3479,14 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
it_painted_region_begin = it_first_painted_region; it_painted_region_begin = it_first_painted_region;
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces); const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
const bool clamp_parent_to_geometry =
layer_has_component_bias &&
layer_geometry_bbox.defined &&
parent_layer_region_bbox.overlap(layer_geometry_bbox);
ExPolygons clamped_parent_expolygons;
if (clamp_parent_to_geometry)
clamped_parent_expolygons = intersection_ex(parent_layer_region.slices.surfaces, layer_geometry_mask);
int self_extruder_id = -1; // 1-based extruder ID int self_extruder_id = -1; // 1-based extruder ID
ExPolygons explicit_self_expolygons; ExPolygons explicit_self_expolygons;
ExPolygons default_self_expolygons; ExPolygons default_self_expolygons;
@@ -3465,7 +3553,11 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
} }
// Trim slices of this LayerRegion with all the MM regions. // Trim slices of this LayerRegion with all the MM regions.
Polygons mine = to_polygons(parent_layer_region.slices.surfaces); // Mixed bias can intentionally shrink a painted layer's true silhouette.
// Clamp the parent region to the post-bias segmentation union so the
// vacated area stays empty instead of falling back to the parent tool.
Polygons mine = clamp_parent_to_geometry ? to_polygons(clamped_parent_expolygons) :
to_polygons(parent_layer_region.slices.surfaces);
for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) { for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) {
const ByExtruder &segmented = by_extruder[extruder_idx]; const ByExtruder &segmented = by_extruder[extruder_idx];
if (!assigned_extruder[extruder_idx]) if (!assigned_extruder[extruder_idx])
+2 -1
View File
@@ -1173,7 +1173,8 @@ std::string string_printf(const char *format, ...)
std::string header_slic3r_generated() std::string header_slic3r_generated()
{ {
return std::string(SLIC3R_APP_NAME " " Snapmaker_VERSION); // Keep generated G-code branded like Snapmaker Orca for printer-side compatibility.
return std::string("Snapmaker Orca ") + Snapmaker_VERSION;
} }
std::string header_gcodeviewer_generated() std::string header_gcodeviewer_generated()
+17 -11
View File
@@ -9364,10 +9364,12 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
for (const LayerRegion* layerm : layer->regions()) { for (const LayerRegion* layerm : layer->regions()) {
if (layerm->slices.surfaces.empty()) if (layerm->slices.surfaces.empty())
continue; continue;
const PrintRegionConfig& cfg = layerm->region().config(); const int effective_wall_filament = int(layerm->extruder(frPerimeter));
if (cfg.wall_filament.value == m_selected_extruder || const int effective_sparse_infill_filament = int(layerm->extruder(frInfill));
cfg.sparse_infill_filament.value == m_selected_extruder || const int effective_solid_infill_filament = int(layerm->extruder(frSolidInfill));
cfg.solid_infill_filament.value == m_selected_extruder ) { if (effective_wall_filament == m_selected_extruder ||
effective_sparse_infill_filament == m_selected_extruder ||
effective_solid_infill_filament == m_selected_extruder) {
at_least_one_has_correct_extruder = true; at_least_one_has_correct_extruder = true;
break; break;
} }
@@ -9389,16 +9391,19 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
for (const LayerRegion *layerm : layer->regions()) { for (const LayerRegion *layerm : layer->regions()) {
if (is_selected_separate_extruder) if (is_selected_separate_extruder)
{ {
const PrintRegionConfig& cfg = layerm->region().config(); const int effective_wall_filament = int(layerm->extruder(frPerimeter));
const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); const int effective_sparse_infill_filament = int(layerm->extruder(frInfill));
if (cfg.wall_filament.value != m_selected_extruder && const int effective_solid_infill_filament = int(layerm->extruder(frSolidInfill));
if (effective_wall_filament != m_selected_extruder &&
effective_sparse_infill_filament != m_selected_extruder && effective_sparse_infill_filament != m_selected_extruder &&
cfg.solid_infill_filament.value != m_selected_extruder) effective_solid_infill_filament != m_selected_extruder)
continue; continue;
} }
if (ctxt.has_perimeters) if (ctxt.has_perimeters) {
const int effective_wall_filament = int(layerm->extruder(frPerimeter));
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
select_geometry(idx_layer, layerm->region().config().wall_filament.value, 0)); select_geometry(idx_layer, effective_wall_filament, 0));
}
if (ctxt.has_infill) { if (ctxt.has_infill) {
for (const ExtrusionEntity *ee : layerm->fills.entities) { for (const ExtrusionEntity *ee : layerm->fills.entities) {
// fill represents infill extrusions of a single island. // fill represents infill extrusions of a single island.
@@ -9406,13 +9411,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
if (! fill->entities.empty()) if (! fill->entities.empty())
{ {
const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); const int effective_sparse_infill_filament = int(layerm->extruder(frInfill));
const int effective_solid_infill_filament = int(layerm->extruder(frSolidInfill));
_3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
select_geometry(idx_layer, select_geometry(idx_layer,
(fill->entities.front()->role() == erSolidInfill && (fill->entities.front()->role() == erSolidInfill &&
std::abs(layerm->region().config().sparse_infill_density.value - 100.) < EPSILON) ? std::abs(layerm->region().config().sparse_infill_density.value - 100.) < EPSILON) ?
int(layerm->extruder(frSolidInfill)) : effective_solid_infill_filament :
(is_solid_infill(fill->entities.front()->role()) ? (is_solid_infill(fill->entities.front()->role()) ?
layerm->region().config().solid_infill_filament : effective_solid_infill_filament :
effective_sparse_infill_filament), effective_sparse_infill_filament),
1)); 1));
} }
+206 -108
View File
@@ -4583,6 +4583,7 @@ public:
const std::vector<double> &nozzle_diameters, const std::vector<double> &nozzle_diameters,
const std::vector<wxColour> &palette, const std::vector<wxColour> &palette,
const MixedFilamentPreviewSettings &preview_settings, const MixedFilamentPreviewSettings &preview_settings,
bool bias_mode_enabled,
OnChangeFn on_change = {}); OnChangeFn on_change = {});
// Get the updated mixed filament data // Get the updated mixed filament data
@@ -4604,6 +4605,7 @@ private:
std::vector<double> m_nozzle_diameters; std::vector<double> m_nozzle_diameters;
std::vector<wxColour> m_palette; std::vector<wxColour> m_palette;
MixedFilamentPreviewSettings m_preview_settings; MixedFilamentPreviewSettings m_preview_settings;
bool m_bias_mode_enabled = false;
bool m_has_changes = false; bool m_has_changes = false;
wxChoice *m_choice_a = nullptr; wxChoice *m_choice_a = nullptr;
@@ -4622,8 +4624,9 @@ private:
wxStaticText *m_picker_b_label = nullptr; wxStaticText *m_picker_b_label = nullptr;
wxStaticText *m_picker_c_label = nullptr; wxStaticText *m_picker_c_label = nullptr;
wxStaticText *m_picker_d_label = nullptr; wxStaticText *m_picker_d_label = nullptr;
wxPanel *m_surface_offset_target_container = nullptr;
wxPanel *m_surface_offset_target_swatch = nullptr; wxPanel *m_surface_offset_target_swatch = nullptr;
wxChoice *m_surface_offset_target_choice = nullptr; wxStaticText *m_surface_offset_target_label = nullptr;
MixedGradientSelector *m_blend_selector = nullptr; MixedGradientSelector *m_blend_selector = nullptr;
wxStaticText *m_blend_label = nullptr; wxStaticText *m_blend_label = nullptr;
wxTextCtrl *m_pattern_ctrl = nullptr; wxTextCtrl *m_pattern_ctrl = nullptr;
@@ -4711,17 +4714,26 @@ private:
return m_fallback; return m_fallback;
} }
int vertical_inset_for_extruder(unsigned int extruder_id, int rect_height) const double max_active_surface_offset_mm() const
{ {
if (extruder_id == 0 || extruder_id >= m_surface_offsets_mm.size() || rect_height <= 2) double max_offset = 0.0;
for (double offset_mm : m_surface_offsets_mm)
max_offset = std::max(max_offset, std::abs(offset_mm));
return std::max(0.001, max_offset);
}
int slot_inset_for_extruder(unsigned int extruder_id, int slot_extent) const
{
if (extruder_id == 0 || extruder_id >= m_surface_offsets_mm.size() || slot_extent <= 2)
return 0; return 0;
const double offset_mm = m_surface_offsets_mm[extruder_id]; const double offset_mm = m_surface_offsets_mm[extruder_id];
if (offset_mm <= 0.0) if (std::abs(offset_mm) <= EPSILON)
return 0; return 0;
const double normalized = std::clamp(offset_mm / 2.0, 0.0, 1.0); const double normalized = std::clamp(std::abs(offset_mm) / max_active_surface_offset_mm(), 0.0, 1.0);
return std::clamp(int(std::round(normalized * rect_height * 0.30)), 0, std::max(0, rect_height / 3)); const int inset = int(std::round(normalized * slot_extent * 0.45)) * (offset_mm < 0.0 ? -1 : 1);
return std::clamp(inset, -std::max(0, slot_extent / 2), std::max(0, slot_extent / 2));
} }
void on_paint(wxPaintEvent &) void on_paint(wxPaintEvent &)
@@ -4747,8 +4759,11 @@ private:
dc.SetBrush(wxBrush(color_for_extruder(m_sequence[idx]))); dc.SetBrush(wxBrush(color_for_extruder(m_sequence[idx])));
const int x = rect.GetLeft() + s * stripe_w; const int x = rect.GetLeft() + s * stripe_w;
const int w = (s == stripes - 1) ? (rect.GetRight() - x + 1) : stripe_w; const int w = (s == stripes - 1) ? (rect.GetRight() - x + 1) : stripe_w;
const int inset_y = vertical_inset_for_extruder(extruder_id, rect.GetHeight()); const int inset = slot_inset_for_extruder(extruder_id, w);
dc.DrawRectangle(x, rect.GetTop() + inset_y, std::max(1, w), std::max(1, rect.GetHeight() - inset_y * 2)); wxRect draw_rect(x + inset / 2, rect.GetTop(), std::max(1, w - inset), rect.GetHeight());
draw_rect.Intersect(rect);
if (draw_rect.GetWidth() > 0)
dc.DrawRectangle(draw_rect);
} }
} else { } else {
const int bars = 24; const int bars = 24;
@@ -4763,8 +4778,11 @@ private:
dc.SetBrush(wxBrush(color_for_extruder(extruder_id))); dc.SetBrush(wxBrush(color_for_extruder(extruder_id)));
const int x = rect.GetLeft() + i * bar_w; const int x = rect.GetLeft() + i * bar_w;
const int w = (i == bars - 1) ? (rect.GetRight() - x + 1) : bar_w; const int w = (i == bars - 1) ? (rect.GetRight() - x + 1) : bar_w;
const int inset_y = vertical_inset_for_extruder(extruder_id, rect.GetHeight()); const int inset = slot_inset_for_extruder(extruder_id, w);
dc.DrawRectangle(x, rect.GetTop() + inset_y, std::max(1, w), std::max(1, rect.GetHeight() - inset_y * 2)); wxRect draw_rect(x + inset / 2, rect.GetTop(), std::max(1, w - inset), rect.GetHeight());
draw_rect.Intersect(rect);
if (draw_rect.GetWidth() > 0)
dc.DrawRectangle(draw_rect);
} }
} }
} }
@@ -5582,8 +5600,11 @@ int MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(const Mixe
} }
static bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf, static bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
const MixedFilamentPreviewSettings &preview_settings) const MixedFilamentPreviewSettings &preview_settings,
bool bias_mode_enabled)
{ {
if (!bias_mode_enabled)
return false;
if (preview_settings.local_z_mode) if (preview_settings.local_z_mode)
return false; return false;
if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme)) if (mf.distribution_mode == int(MixedFilament::SameLayerPointillisme))
@@ -5595,24 +5616,6 @@ static bool mixed_filament_supports_bias_apparent_color(const MixedFilament &mf,
return mf.component_a >= 1 && mf.component_b >= 1 && mf.component_a != mf.component_b; return mf.component_a >= 1 && mf.component_b >= 1 && mf.component_a != mf.component_b;
} }
static std::pair<int, float> mixed_filament_single_surface_offset_ui_state(const MixedFilament &mf)
{
const float offset_a = mf.component_a_surface_offset;
const float offset_b = mf.component_b_surface_offset;
const bool has_a = std::abs(offset_a) > 1e-6f;
const bool has_b = std::abs(offset_b) > 1e-6f;
if (has_b && (!has_a || std::abs(offset_b) > std::abs(offset_a)))
return { 1, offset_b };
return { 0, has_a ? offset_a : 0.f };
}
static std::pair<float, float> mixed_filament_single_surface_offset_pair(int target_index, float value)
{
const float clamped = std::clamp(value, -2.f, 2.f);
return target_index == 1 ? std::make_pair(0.f, clamped) : std::make_pair(clamped, 0.f);
}
static double mixed_filament_reference_nozzle_mm(unsigned int component_a, static double mixed_filament_reference_nozzle_mm(unsigned int component_a,
unsigned int component_b, unsigned int component_b,
const std::vector<double> &nozzle_diameters) const std::vector<double> &nozzle_diameters)
@@ -5633,12 +5636,37 @@ static double mixed_filament_reference_nozzle_mm(unsigned int comp
return std::accumulate(samples.begin(), samples.end(), 0.0) / double(samples.size()); return std::accumulate(samples.begin(), samples.end(), 0.0) / double(samples.size());
} }
static double mixed_filament_bias_limit_mm(const MixedFilament &mf, const std::vector<double> &nozzle_diameters)
{
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
return MixedFilamentManager::max_pair_bias_mm(float(reference_nozzle_mm));
}
static float mixed_filament_single_surface_offset_value(const MixedFilament &mf,
const std::vector<double> &nozzle_diameters)
{
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
return MixedFilamentManager::bias_ui_value_from_surface_offsets(
mf.component_a_surface_offset,
mf.component_b_surface_offset,
float(reference_nozzle_mm));
}
static std::pair<float, float> mixed_filament_single_surface_offset_pair(const MixedFilament &mf,
float value,
const std::vector<double> &nozzle_diameters)
{
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
return MixedFilamentManager::surface_offset_pair_from_signed_bias(value, float(reference_nozzle_mm));
}
static std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf, static std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedFilament &mf,
const MixedFilamentPreviewSettings &preview_settings, const MixedFilamentPreviewSettings &preview_settings,
const std::vector<double> &nozzle_diameters) const std::vector<double> &nozzle_diameters,
bool bias_mode_enabled)
{ {
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings); const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings)) if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
return { 100 - base_b, base_b }; return { 100 - base_b, base_b };
const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters); const double reference_nozzle_mm = mixed_filament_reference_nozzle_mm(mf.component_a, mf.component_b, nozzle_diameters);
@@ -5651,15 +5679,16 @@ static std::pair<int, int> mixed_filament_apparent_pair_percentages(const MixedF
static std::string mixed_filament_apparent_pair_summary(const MixedFilament &mf, static std::string mixed_filament_apparent_pair_summary(const MixedFilament &mf,
const MixedFilamentPreviewSettings &preview_settings, const MixedFilamentPreviewSettings &preview_settings,
const std::vector<double> &nozzle_diameters) const std::vector<double> &nozzle_diameters,
bool bias_mode_enabled)
{ {
if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings)) if (!mixed_filament_supports_bias_apparent_color(mf, preview_settings, bias_mode_enabled))
return {}; return {};
const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings); const int base_b = MixedFilamentConfigPanel::effective_local_z_preview_mix_b_percent(mf, preview_settings);
const int base_a = 100 - base_b; const int base_a = 100 - base_b;
const auto [apparent_a, apparent_b] = const auto [apparent_a, apparent_b] =
mixed_filament_apparent_pair_percentages(mf, preview_settings, nozzle_diameters); mixed_filament_apparent_pair_percentages(mf, preview_settings, nozzle_diameters, bias_mode_enabled);
if (std::abs(mf.component_a_surface_offset - mf.component_b_surface_offset) > 1e-4f && if (std::abs(mf.component_a_surface_offset - mf.component_b_surface_offset) > 1e-4f &&
(apparent_a != base_a || apparent_b != base_b)) { (apparent_a != base_a || apparent_b != base_b)) {
@@ -5883,6 +5912,7 @@ MixedFilamentConfigPanel::MixedFilamentConfigPanel(wxWindow *parent,
const std::vector<double> &nozzle_diameters, const std::vector<double> &nozzle_diameters,
const std::vector<wxColour> &palette, const std::vector<wxColour> &palette,
const MixedFilamentPreviewSettings &preview_settings, const MixedFilamentPreviewSettings &preview_settings,
bool bias_mode_enabled,
OnChangeFn on_change) OnChangeFn on_change)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE)
, m_mixed_id(mixed_id) , m_mixed_id(mixed_id)
@@ -5892,6 +5922,7 @@ MixedFilamentConfigPanel::MixedFilamentConfigPanel(wxWindow *parent,
, m_nozzle_diameters(nozzle_diameters) , m_nozzle_diameters(nozzle_diameters)
, m_palette(palette) , m_palette(palette)
, m_preview_settings(preview_settings) , m_preview_settings(preview_settings)
, m_bias_mode_enabled(bias_mode_enabled)
, m_selected_weight_state(std::make_shared<std::vector<int>>()) , m_selected_weight_state(std::make_shared<std::vector<int>>())
, m_on_change(on_change) , m_on_change(on_change)
{ {
@@ -5905,6 +5936,7 @@ MixedFilamentConfigPanel::MixedFilamentConfigPanel(wxWindow *parent,
void MixedFilamentConfigPanel::build_ui() void MixedFilamentConfigPanel::build_ui()
{ {
const int gap = FromDIP(6); const int gap = FromDIP(6);
const int compact_gap = std::max(FromDIP(2), gap / 3);
const bool is_dark = wxGetApp().dark_mode(); const bool is_dark = wxGetApp().dark_mode();
const wxColour panel_bg = GetBackgroundColour().IsOk() ? GetBackgroundColour() : const wxColour panel_bg = GetBackgroundColour().IsOk() ? GetBackgroundColour() :
(is_dark ? wxColour(52, 52, 56) : wxColour(255, 255, 255)); (is_dark ? wxColour(52, 52, 56) : wxColour(255, 255, 255));
@@ -5967,8 +5999,10 @@ void MixedFilamentConfigPanel::build_ui()
auto *container_sizer = new wxBoxSizer(wxHORIZONTAL); auto *container_sizer = new wxBoxSizer(wxHORIZONTAL);
swatch_out = new wxPanel(container_out, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(12), FromDIP(12)), wxBORDER_SIMPLE); swatch_out = new wxPanel(container_out, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(12), FromDIP(12)), wxBORDER_SIMPLE);
swatch_out->SetMinSize(wxSize(FromDIP(12), FromDIP(12))); swatch_out->SetMinSize(wxSize(FromDIP(12), FromDIP(12)));
swatch_out->SetToolTip(tooltip);
label_out = new wxStaticText(container_out, wxID_ANY, wxEmptyString); label_out = new wxStaticText(container_out, wxID_ANY, wxEmptyString);
label_out->SetForegroundColour(local_picker_text); label_out->SetForegroundColour(local_picker_text);
label_out->SetToolTip(tooltip);
auto *content_sizer = new wxBoxSizer(wxHORIZONTAL); auto *content_sizer = new wxBoxSizer(wxHORIZONTAL);
content_sizer->Add(swatch_out, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, inner_gap); content_sizer->Add(swatch_out, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, inner_gap);
@@ -6087,59 +6121,68 @@ void MixedFilamentConfigPanel::build_ui()
auto *preview_row = new wxBoxSizer(wxHORIZONTAL); auto *preview_row = new wxBoxSizer(wxHORIZONTAL);
m_mix_preview = new MixedMixPreview(this); m_mix_preview = new MixedMixPreview(this);
m_mix_preview->SetBackgroundColour(panel_bg); m_mix_preview->SetBackgroundColour(panel_bg);
preview_row->Add(m_mix_preview, 3, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); preview_row->Add(m_mix_preview, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap);
auto *bias_controls = new wxBoxSizer(wxHORIZONTAL); auto *bias_controls = new wxBoxSizer(wxHORIZONTAL);
const auto initial_surface_offset_state = mixed_filament_single_surface_offset_ui_state(m_mf); const float initial_surface_offset_value = mixed_filament_single_surface_offset_value(m_mf, m_nozzle_diameters);
const double initial_bias_limit = mixed_filament_bias_limit_mm(m_mf, m_nozzle_diameters);
const wxString bias_tooltip =
_L("Positive bias recesses the second filament in the pair; negative bias recesses the first filament.\n\n"
"The color chip shows which filament the current value affects.\n\n"
"Grouped wall patterns, same-layer pointillisme, and Local-Z dithering ignore it.");
auto *surface_offset_label = new wxStaticText(this, wxID_ANY, _L("XY-Bias")); auto *surface_offset_label = new wxStaticText(this, wxID_ANY, _L("Bias"));
surface_offset_label->SetForegroundColour(is_dark ? wxColour(236, 236, 236) : wxColour(20, 20, 20)); surface_offset_label->SetForegroundColour(is_dark ? wxColour(236, 236, 236) : wxColour(20, 20, 20));
surface_offset_label->SetToolTip( surface_offset_label->SetToolTip(bias_tooltip);
_L("Additional XY offset applied only on layers where this mixed filament resolves to component A or B.\n\n" bias_controls->Add(surface_offset_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap);
"Positive values contract inward. Negative values expand outward.\n\n"
"Currently this is intended for ordinary layer/height cadence rows. Grouped wall patterns, same-layer pointillisme, and Local-Z dithering ignore it."));
bias_controls->Add(surface_offset_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(4), gap / 2));
auto *target_group = new wxBoxSizer(wxHORIZONTAL); create_component_picker(m_surface_offset_target_container,
m_surface_offset_target_swatch = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(10), FromDIP(10)), wxBORDER_SIMPLE); m_surface_offset_target_swatch,
m_surface_offset_target_swatch->SetMinSize(wxSize(FromDIP(10), FromDIP(10))); m_surface_offset_target_label,
m_surface_offset_target_swatch->SetMaxSize(wxSize(FromDIP(10), FromDIP(10))); bias_tooltip);
m_surface_offset_target_swatch->SetToolTip(_L("Choose which filament in this pair receives the XY bias.")); if (m_surface_offset_target_container)
target_group->Add(m_surface_offset_target_swatch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3)); m_surface_offset_target_container->SetCursor(wxCursor(wxCURSOR_ARROW));
if (m_surface_offset_target_swatch)
m_surface_offset_target_swatch->SetCursor(wxCursor(wxCURSOR_ARROW));
if (m_surface_offset_target_label)
m_surface_offset_target_label->SetCursor(wxCursor(wxCURSOR_ARROW));
bias_controls->Add(m_surface_offset_target_container, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap);
wxArrayString surface_offset_targets; m_surface_offset_spin = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(58), -1),
surface_offset_targets.Add(wxString::Format("F%d", int(m_mf.component_a)));
surface_offset_targets.Add(wxString::Format("F%d", int(m_mf.component_b)));
m_surface_offset_target_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(62), -1), surface_offset_targets);
m_surface_offset_target_choice->SetSelection(std::clamp(initial_surface_offset_state.first, 0, 1));
m_surface_offset_target_choice->SetToolTip(_L("Choose which filament in this pair receives the XY bias."));
target_group->Add(m_surface_offset_target_choice, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
bias_controls->Add(target_group, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4));
m_surface_offset_spin = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(64), -1),
wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER,
-2.0, 2.0, std::clamp(double(initial_surface_offset_state.second), -2.0, 2.0), 0.01); -initial_bias_limit, initial_bias_limit,
std::clamp(double(initial_surface_offset_value), -initial_bias_limit, initial_bias_limit), 0.001);
m_surface_offset_spin->SetDigits(3); m_surface_offset_spin->SetDigits(3);
m_surface_offset_spin->SetToolTip( m_surface_offset_spin->SetToolTip(bias_tooltip);
_L("XY bias for the selected filament on layers where this row resolves to it. Positive values contract inward. Negative values expand outward.")); bias_controls->Add(m_surface_offset_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap);
bias_controls->Add(m_surface_offset_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(6));
auto *surface_offset_units = new wxStaticText(this, wxID_ANY, _L("mm")); auto *surface_offset_units = new wxStaticText(this, wxID_ANY, _L("mm"));
surface_offset_units->SetForegroundColour(is_dark ? wxColour(210, 210, 210) : wxColour(72, 72, 72)); surface_offset_units->SetForegroundColour(is_dark ? wxColour(210, 210, 210) : wxColour(72, 72, 72));
surface_offset_units->SetToolTip(bias_tooltip);
bias_controls->Add(surface_offset_units, 0, wxALIGN_CENTER_VERTICAL); bias_controls->Add(surface_offset_units, 0, wxALIGN_CENTER_VERTICAL);
preview_row->Add(bias_controls, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); if (m_bias_mode_enabled)
preview_row->Add(bias_controls, 0, wxALIGN_CENTER_VERTICAL);
else {
surface_offset_label->Hide();
if (m_surface_offset_target_container)
m_surface_offset_target_container->Hide();
if (m_surface_offset_spin)
m_surface_offset_spin->Hide();
surface_offset_units->Hide();
}
root->Add(preview_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); root->Add(preview_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
const auto initial_surface_offset_pair = if (m_bias_mode_enabled) {
mixed_filament_single_surface_offset_pair(initial_surface_offset_state.first, initial_surface_offset_state.second); const auto initial_surface_offset_pair =
m_mf.component_a_surface_offset = initial_surface_offset_pair.first; mixed_filament_single_surface_offset_pair(m_mf, initial_surface_offset_value, m_nozzle_diameters);
m_mf.component_b_surface_offset = initial_surface_offset_pair.second; m_mf.component_a_surface_offset = initial_surface_offset_pair.first;
m_mf.component_b_surface_offset = initial_surface_offset_pair.second;
}
const bool initial_component_surface_offsets_supported = !pattern_row_mode && const bool initial_component_surface_offsets_supported = m_bias_mode_enabled &&
!pattern_row_mode &&
row_distribution_mode != int(MixedFilament::SameLayerPointillisme) && row_distribution_mode != int(MixedFilament::SameLayerPointillisme) &&
!m_preview_settings.local_z_mode; !m_preview_settings.local_z_mode;
if (m_surface_offset_target_choice)
m_surface_offset_target_choice->Enable(initial_component_surface_offsets_supported);
if (m_surface_offset_spin) if (m_surface_offset_spin)
m_surface_offset_spin->Enable(initial_component_surface_offsets_supported); m_surface_offset_spin->Enable(initial_component_surface_offsets_supported);
@@ -6179,6 +6222,18 @@ void MixedFilamentConfigPanel::build_ui()
auto apply_changes = [this]() { auto apply_changes = [this]() {
m_has_changes = true; m_has_changes = true;
double surface_offset_value = 0.0;
if (m_surface_offset_spin) {
surface_offset_value = m_surface_offset_spin->GetValue();
#if !defined(wxHAS_NATIVE_SPINCTRLDOUBLE)
if (wxTextCtrl *text = m_surface_offset_spin->GetText()) {
double parsed_value = 0.0;
if (text->GetValue().ToDouble(&parsed_value))
surface_offset_value = parsed_value;
}
#endif
}
int a = std::clamp(m_choice_a->GetSelection() + 1, 1, int(m_num_physical)); int a = std::clamp(m_choice_a->GetSelection() + 1, 1, int(m_num_physical));
int b = std::clamp(m_choice_b->GetSelection() + 1, 1, int(m_num_physical)); int b = std::clamp(m_choice_b->GetSelection() + 1, 1, int(m_num_physical));
if (a == b && m_num_physical > 1) { if (a == b && m_num_physical > 1) {
@@ -6194,14 +6249,16 @@ void MixedFilamentConfigPanel::build_ui()
const bool preserve_same_layer_mode = m_mf.distribution_mode == int(MixedFilament::SameLayerPointillisme); const bool preserve_same_layer_mode = m_mf.distribution_mode == int(MixedFilament::SameLayerPointillisme);
m_mf.component_a = unsigned(a); m_mf.component_a = unsigned(a);
m_mf.component_b = unsigned(b); m_mf.component_b = unsigned(b);
const int surface_offset_target_index = if (m_bias_mode_enabled) {
m_surface_offset_target_choice ? std::clamp(m_surface_offset_target_choice->GetSelection(), 0, 1) : 0; const double bias_limit = mixed_filament_bias_limit_mm(m_mf, m_nozzle_diameters);
const float surface_offset_value = const float clamped_surface_offset_value = std::clamp(float(surface_offset_value), -float(bias_limit), float(bias_limit));
m_surface_offset_spin ? std::clamp(float(m_surface_offset_spin->GetValue()), -2.f, 2.f) : 0.f; const auto surface_offset_pair =
const auto surface_offset_pair = mixed_filament_single_surface_offset_pair(m_mf, clamped_surface_offset_value, m_nozzle_diameters);
mixed_filament_single_surface_offset_pair(surface_offset_target_index, surface_offset_value); m_mf.component_a_surface_offset = surface_offset_pair.first;
m_mf.component_a_surface_offset = surface_offset_pair.first; m_mf.component_b_surface_offset = surface_offset_pair.second;
m_mf.component_b_surface_offset = surface_offset_pair.second; if (m_surface_offset_spin)
m_surface_offset_spin->SetValue(clamped_surface_offset_value);
}
m_mf.local_z_max_sublayers = m_mf.local_z_max_sublayers =
(m_local_z_limit_checkbox != nullptr && m_local_z_limit_checkbox->GetValue() && m_local_z_limit_spin != nullptr) ? (m_local_z_limit_checkbox != nullptr && m_local_z_limit_checkbox->GetValue() && m_local_z_limit_spin != nullptr) ?
std::max(2, m_local_z_limit_spin->GetValue()) : std::max(2, m_local_z_limit_spin->GetValue()) :
@@ -6290,11 +6347,10 @@ void MixedFilamentConfigPanel::build_ui()
m_mf.custom = true; m_mf.custom = true;
const std::vector<unsigned int> selected_gradient_ids = decode_gradient_ids(m_mf.gradient_component_ids); const std::vector<unsigned int> selected_gradient_ids = decode_gradient_ids(m_mf.gradient_component_ids);
const bool component_surface_offsets_supported = (m_pattern_ctrl == nullptr) && const bool component_surface_offsets_supported = m_bias_mode_enabled &&
(m_pattern_ctrl == nullptr) &&
!same_layer_mode && !same_layer_mode &&
!m_preview_settings.local_z_mode; !m_preview_settings.local_z_mode;
if (m_surface_offset_target_choice)
m_surface_offset_target_choice->Enable(component_surface_offsets_supported);
if (m_surface_offset_spin) if (m_surface_offset_spin)
m_surface_offset_spin->Enable(component_surface_offsets_supported); m_surface_offset_spin->Enable(component_surface_offsets_supported);
if (preview_sequence.empty()) if (preview_sequence.empty())
@@ -6311,11 +6367,11 @@ void MixedFilamentConfigPanel::build_ui()
m_blend_selector->set_multi_preview(corner_colors, *m_selected_weight_state); m_blend_selector->set_multi_preview(corner_colors, *m_selected_weight_state);
} }
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings) && if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
m_mf.component_a >= 1 && m_mf.component_b >= 1 && m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) { m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
const auto [apparent_pct_a, apparent_pct_b] = const auto [apparent_pct_a, apparent_pct_b] =
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters); mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
m_mf.display_color = MixedFilamentManager::blend_color( m_mf.display_color = MixedFilamentManager::blend_color(
m_physical_colors[size_t(m_mf.component_a - 1)], m_physical_colors[size_t(m_mf.component_a - 1)],
m_physical_colors[size_t(m_mf.component_b - 1)], m_physical_colors[size_t(m_mf.component_b - 1)],
@@ -6344,13 +6400,14 @@ void MixedFilamentConfigPanel::build_ui()
} }
if (m_mix_preview) { if (m_mix_preview) {
const std::string bias_summary = mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters); const std::string bias_summary =
mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
const std::string summary = bias_summary.empty() ? summarize_sequence(preview_sequence) : bias_summary; const std::string summary = bias_summary.empty() ? summarize_sequence(preview_sequence) : bias_summary;
std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0); std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0);
if (m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size()) if (m_bias_mode_enabled && m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size())
preview_surface_offsets[m_mf.component_a] = std::max(0.0, double(m_mf.component_a_surface_offset)); preview_surface_offsets[m_mf.component_a] = double(m_mf.component_a_surface_offset);
if (m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size()) if (m_bias_mode_enabled && m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size())
preview_surface_offsets[m_mf.component_b] = std::max(0.0, double(m_mf.component_b_surface_offset)); preview_surface_offsets[m_mf.component_b] = double(m_mf.component_b_surface_offset);
m_mix_preview->set_data(m_palette, preview_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color), m_mix_preview->set_data(m_palette, preview_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color),
_L("Preview"), summary.empty() ? wxString() : from_u8(summary)); _L("Preview"), summary.empty() ? wxString() : from_u8(summary));
} }
@@ -6448,8 +6505,6 @@ void MixedFilamentConfigPanel::build_ui()
evt.Skip(); evt.Skip();
}); });
} }
if (m_surface_offset_target_choice)
m_surface_offset_target_choice->Bind(wxEVT_CHOICE, [apply_changes](wxCommandEvent &) { apply_changes(); });
if (m_surface_offset_spin) { if (m_surface_offset_spin) {
m_surface_offset_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_changes](wxSpinDoubleEvent &) { apply_changes(); }); m_surface_offset_spin->Bind(wxEVT_SPINCTRLDOUBLE, [apply_changes](wxSpinDoubleEvent &) { apply_changes(); });
m_surface_offset_spin->Bind(wxEVT_TEXT_ENTER, [apply_changes](wxCommandEvent &) { apply_changes(); }); m_surface_offset_spin->Bind(wxEVT_TEXT_ENTER, [apply_changes](wxCommandEvent &) { apply_changes(); });
@@ -6503,6 +6558,7 @@ void MixedFilamentConfigPanel::build_ui()
} }
} }
update_component_picker_visuals();
SetSizer(root); SetSizer(root);
Layout(); Layout();
SetMinSize(wxSize(-1, GetBestSize().GetHeight())); SetMinSize(wxSize(-1, GetBestSize().GetHeight()));
@@ -6557,22 +6613,33 @@ void MixedFilamentConfigPanel::update_component_picker_visuals()
update_one(m_choice_c, m_picker_c_container, m_picker_c_swatch, m_picker_c_label); update_one(m_choice_c, m_picker_c_container, m_picker_c_swatch, m_picker_c_label);
update_one(m_choice_d, m_picker_d_container, m_picker_d_swatch, m_picker_d_label); update_one(m_choice_d, m_picker_d_container, m_picker_d_swatch, m_picker_d_label);
if (m_surface_offset_target_choice) { if (m_surface_offset_target_container || m_surface_offset_target_swatch || m_surface_offset_target_label || m_surface_offset_spin) {
const int a_filament = std::clamp(m_choice_a ? (m_choice_a->GetSelection() + 1) : int(m_mf.component_a), 1, int(std::max<size_t>(1, m_num_physical))); const int a_filament = std::clamp(m_choice_a ? (m_choice_a->GetSelection() + 1) : int(m_mf.component_a), 1, int(std::max<size_t>(1, m_num_physical)));
const int b_filament = std::clamp(m_choice_b ? (m_choice_b->GetSelection() + 1) : int(m_mf.component_b), 1, int(std::max<size_t>(1, m_num_physical))); const int b_filament = std::clamp(m_choice_b ? (m_choice_b->GetSelection() + 1) : int(m_mf.component_b), 1, int(std::max<size_t>(1, m_num_physical)));
if (m_surface_offset_target_choice->GetCount() >= 2) { MixedFilament active_pair = m_mf;
m_surface_offset_target_choice->SetString(0, wxString::Format("F%d", a_filament)); active_pair.component_a = unsigned(a_filament);
m_surface_offset_target_choice->SetString(1, wxString::Format("F%d", b_filament)); active_pair.component_b = unsigned(b_filament);
double signed_bias_value = mixed_filament_single_surface_offset_value(active_pair, m_nozzle_diameters);
if (m_surface_offset_spin && m_bias_mode_enabled) {
const double bias_limit = mixed_filament_bias_limit_mm(active_pair, m_nozzle_diameters);
m_surface_offset_spin->SetRange(-bias_limit, bias_limit);
signed_bias_value = m_surface_offset_spin->GetValue();
} }
const int selected_target = std::clamp(m_surface_offset_target_choice->GetSelection(), 0, 1); const int active_filament = signed_bias_value < -EPSILON ? a_filament : b_filament;
const int color_idx = (selected_target == 1 ? b_filament : a_filament) - 1; const int color_idx = active_filament - 1;
const wxColour color = (color_idx >= 0 && size_t(color_idx) < m_palette.size()) ? m_palette[size_t(color_idx)] : wxColour("#26A69A"); const wxColour color = (color_idx >= 0 && size_t(color_idx) < m_palette.size()) ? m_palette[size_t(color_idx)] : wxColour("#26A69A");
if (m_surface_offset_target_swatch) { if (m_surface_offset_target_swatch) {
m_surface_offset_target_swatch->SetBackgroundColour(color); m_surface_offset_target_swatch->SetBackgroundColour(color);
m_surface_offset_target_swatch->Refresh(); m_surface_offset_target_swatch->Refresh();
} }
m_surface_offset_target_choice->Refresh(); if (m_surface_offset_target_label)
m_surface_offset_target_label->SetLabel(wxString::Format("F%d", active_filament));
if (m_surface_offset_target_container) {
m_surface_offset_target_container->Layout();
m_surface_offset_target_container->Refresh();
}
} }
} }
@@ -6613,11 +6680,11 @@ void MixedFilamentConfigPanel::update_preview()
} }
if (m_mix_preview) { if (m_mix_preview) {
if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings) && if (mixed_filament_supports_bias_apparent_color(m_mf, m_preview_settings, m_bias_mode_enabled) &&
m_mf.component_a >= 1 && m_mf.component_b >= 1 && m_mf.component_a >= 1 && m_mf.component_b >= 1 &&
m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) { m_mf.component_a <= m_physical_colors.size() && m_mf.component_b <= m_physical_colors.size()) {
const auto [apparent_pct_a, apparent_pct_b] = const auto [apparent_pct_a, apparent_pct_b] =
mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters); mixed_filament_apparent_pair_percentages(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
m_mf.display_color = MixedFilamentManager::blend_color( m_mf.display_color = MixedFilamentManager::blend_color(
m_physical_colors[size_t(m_mf.component_a - 1)], m_physical_colors[size_t(m_mf.component_a - 1)],
m_physical_colors[size_t(m_mf.component_b - 1)], m_physical_colors[size_t(m_mf.component_b - 1)],
@@ -6625,13 +6692,14 @@ void MixedFilamentConfigPanel::update_preview()
apparent_pct_b); apparent_pct_b);
} }
const std::string bias_summary = mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters); const std::string bias_summary =
mixed_filament_apparent_pair_summary(m_mf, m_preview_settings, m_nozzle_diameters, m_bias_mode_enabled);
const std::string summary = bias_summary.empty() ? summarize_sequence(initial_sequence) : bias_summary; const std::string summary = bias_summary.empty() ? summarize_sequence(initial_sequence) : bias_summary;
std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0); std::vector<double> preview_surface_offsets(m_palette.size() + 1, 0.0);
if (m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size()) if (m_bias_mode_enabled && m_mf.component_a >= 1 && m_mf.component_a < preview_surface_offsets.size())
preview_surface_offsets[m_mf.component_a] = std::max(0.0, double(m_mf.component_a_surface_offset)); preview_surface_offsets[m_mf.component_a] = double(m_mf.component_a_surface_offset);
if (m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size()) if (m_bias_mode_enabled && m_mf.component_b >= 1 && m_mf.component_b < preview_surface_offsets.size())
preview_surface_offsets[m_mf.component_b] = std::max(0.0, double(m_mf.component_b_surface_offset)); preview_surface_offsets[m_mf.component_b] = double(m_mf.component_b_surface_offset);
m_mix_preview->set_data(m_palette, initial_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color), m_mix_preview->set_data(m_palette, initial_sequence, same_layer_mode, preview_surface_offsets, wxColour(m_mf.display_color),
_L("Preview"), summary.empty() ? wxString() : from_u8(summary)); _L("Preview"), summary.empty() ? wxString() : from_u8(summary));
} }
@@ -6870,6 +6938,22 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
else else
preset_bundle->project_config.set_key_value(key, new ConfigOptionString(value)); preset_bundle->project_config.set_key_value(key, new ConfigOptionString(value));
}; };
auto set_mixed_bool = [preset_bundle, print_cfg](const std::string &key, bool value) {
if (print_cfg) {
if (ConfigOptionBool *opt = print_cfg->option<ConfigOptionBool>(key))
opt->value = value;
else if (ConfigOptionInt *opt = print_cfg->option<ConfigOptionInt>(key))
opt->value = value ? 1 : 0;
else
print_cfg->set_key_value(key, new ConfigOptionBool(value));
}
if (ConfigOptionBool *opt = preset_bundle->project_config.option<ConfigOptionBool>(key))
opt->value = value;
else if (ConfigOptionInt *opt = preset_bundle->project_config.option<ConfigOptionInt>(key))
opt->value = value ? 1 : 0;
else
preset_bundle->project_config.set_key_value(key, new ConfigOptionBool(value));
};
auto set_mixed_mode = [preset_bundle, print_cfg](bool enabled) { auto set_mixed_mode = [preset_bundle, print_cfg](bool enabled) {
if (print_cfg) { if (print_cfg) {
if (ConfigOptionBool *opt = print_cfg->option<ConfigOptionBool>("mixed_filament_gradient_mode")) if (ConfigOptionBool *opt = print_cfg->option<ConfigOptionBool>("mixed_filament_gradient_mode"))
@@ -7088,6 +7172,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
if (print_cfg && print_cfg->has("wall_loops")) if (print_cfg && print_cfg->has("wall_loops"))
wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops")))); wall_loops = std::max<size_t>(1, size_t(std::max(1, print_cfg->opt_int("wall_loops"))));
const bool local_z_mode = get_mixed_bool("dithering_local_z_mode", false); const bool local_z_mode = get_mixed_bool("dithering_local_z_mode", false);
const bool component_bias_enabled = get_mixed_bool("mixed_filament_component_bias_enabled", false);
float pointillism_pixel_size = std::max(0.f, get_mixed_float("mixed_filament_pointillism_pixel_size", 0.f)); 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 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); float mixed_surface_indentation = std::clamp(get_mixed_float("mixed_filament_surface_indentation", 0.f), -2.f, 2.f);
@@ -7185,13 +7270,14 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme); const bool same_layer_mode = entry.distribution_mode == int(MixedFilament::SameLayerPointillisme);
return build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode); return build_effective_pair_preview_sequence(entry.component_a, entry.component_b, effective_mix_b, same_layer_mode);
}; };
auto compute_entry_display_color = [num_physical, &physical_colors, &nozzle_diameters, blend_from_sequence, build_entry_preview_sequence, preview_settings](const MixedFilament &entry) { auto compute_entry_display_color = [num_physical, &physical_colors, &nozzle_diameters, blend_from_sequence, build_entry_preview_sequence,
if (mixed_filament_supports_bias_apparent_color(entry, preview_settings) && preview_settings, component_bias_enabled](const MixedFilament &entry) {
if (mixed_filament_supports_bias_apparent_color(entry, preview_settings, component_bias_enabled) &&
entry.component_a >= 1 && entry.component_b >= 1 && entry.component_a >= 1 && entry.component_b >= 1 &&
entry.component_a <= num_physical && entry.component_b <= num_physical && entry.component_a <= num_physical && entry.component_b <= num_physical &&
entry.component_a <= physical_colors.size() && entry.component_b <= physical_colors.size()) { entry.component_a <= physical_colors.size() && entry.component_b <= physical_colors.size()) {
const auto [apparent_pct_a, apparent_pct_b] = const auto [apparent_pct_a, apparent_pct_b] =
mixed_filament_apparent_pair_percentages(entry, preview_settings, nozzle_diameters); mixed_filament_apparent_pair_percentages(entry, preview_settings, nozzle_diameters, component_bias_enabled);
return MixedFilamentManager::blend_color( return MixedFilamentManager::blend_color(
physical_colors[entry.component_a - 1], physical_colors[entry.component_a - 1],
physical_colors[entry.component_b - 1], physical_colors[entry.component_b - 1],
@@ -7225,11 +7311,21 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
mixed_mgr.apply_gradient_settings(gradient_mode, lower_bound, upper_bound, advanced_dithering); mixed_mgr.apply_gradient_settings(gradient_mode, lower_bound, upper_bound, advanced_dithering);
} }
if (component_bias_enabled) {
for (MixedFilament &entry : mixed_mgr.mixed_filaments()) {
const float bias_value = mixed_filament_single_surface_offset_value(entry, nozzle_diameters);
const auto balanced_pair = mixed_filament_single_surface_offset_pair(entry, bias_value, nozzle_diameters);
entry.component_a_surface_offset = balanced_pair.first;
entry.component_b_surface_offset = balanced_pair.second;
}
}
// During project load, sidebar may refresh before physical filament combos // During project load, sidebar may refresh before physical filament combos
// finish syncing. Avoid overwriting persisted mixed definitions while the // finish syncing. Avoid overwriting persisted mixed definitions while the
// physical filament set is incomplete. // physical filament set is incomplete.
if (num_physical >= 2) { if (num_physical >= 2) {
set_mixed_mode(height_weighted_mode); set_mixed_mode(height_weighted_mode);
set_mixed_bool("mixed_filament_component_bias_enabled", component_bias_enabled);
set_mixed_float("mixed_filament_height_lower_bound", lower_bound); set_mixed_float("mixed_filament_height_lower_bound", lower_bound);
set_mixed_float("mixed_filament_height_upper_bound", upper_bound); set_mixed_float("mixed_filament_height_upper_bound", upper_bound);
set_mixed_float("mixed_color_layer_height_a", preferred_local_z_a); set_mixed_float("mixed_color_layer_height_a", preferred_local_z_a);
@@ -7640,7 +7736,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
return row->GetClientRect().Contains(local); return row->GetClientRect().Contains(local);
}; };
auto ensure_editor = [this, mixed_id, num_physical, physical_colors, nozzle_diameters, palette, preview_settings, preset_bundle, auto ensure_editor = [this, mixed_id, num_physical, physical_colors, nozzle_diameters, palette, preview_settings, component_bias_enabled, preset_bundle,
editor_host, editor_sizer, swatch, summary_label, header_panel, row, editor_host, editor_sizer, swatch, summary_label, header_panel, row,
rows_scroller, mixed_summary_text, apply_mixed_entry_changes]() { rows_scroller, mixed_summary_text, apply_mixed_entry_changes]() {
if (!preset_bundle || !editor_sizer || editor_sizer->GetItemCount() > 0) if (!preset_bundle || !editor_sizer || editor_sizer->GetItemCount() > 0)
@@ -7652,6 +7748,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager)
return; return;
auto *editor = new MixedFilamentConfigPanel(editor_host, mixed_id, mfs[mixed_id], num_physical, physical_colors, nozzle_diameters, palette, preview_settings, auto *editor = new MixedFilamentConfigPanel(editor_host, mixed_id, mfs[mixed_id], num_physical, physical_colors, nozzle_diameters, palette, preview_settings,
component_bias_enabled,
[this, mixed_id, swatch, summary_label, header_panel, row, rows_scroller, mixed_summary_text, apply_mixed_entry_changes](const MixedFilament &updated_mf) { [this, mixed_id, swatch, summary_label, header_panel, row, rows_scroller, mixed_summary_text, apply_mixed_entry_changes](const MixedFilament &updated_mf) {
apply_mixed_entry_changes(mixed_id, updated_mf, true); apply_mixed_entry_changes(mixed_id, updated_mf, true);
@@ -10360,6 +10457,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
"mixed_filament_advanced_dithering", "mixed_filament_advanced_dithering",
"mixed_filament_pointillism_pixel_size", "mixed_filament_pointillism_pixel_size",
"mixed_filament_pointillism_line_gap", "mixed_filament_pointillism_line_gap",
"mixed_filament_component_bias_enabled",
"mixed_filament_surface_indentation" "mixed_filament_surface_indentation"
}; };
preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true); preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true);
+8
View File
@@ -1793,6 +1793,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
return; return;
} }
const bool refresh_mixed_filament_panel =
m_type == Preset::TYPE_PRINT && opt_key == "mixed_filament_component_bias_enabled";
// Keep Mixed Filaments global settings in sync with project_config. In // Keep Mixed Filaments global settings in sync with project_config. In
// full_fff_config(), project_config is applied last and would otherwise // full_fff_config(), project_config is applied last and would otherwise
// override the edited print preset value from the Others panel. // override the edited print preset value from the Others panel.
@@ -1805,6 +1808,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_advanced_dithering" ||
opt_key == "mixed_filament_pointillism_pixel_size" || opt_key == "mixed_filament_pointillism_pixel_size" ||
opt_key == "mixed_filament_pointillism_line_gap" || opt_key == "mixed_filament_pointillism_line_gap" ||
opt_key == "mixed_filament_component_bias_enabled" ||
opt_key == "mixed_filament_surface_indentation" || opt_key == "mixed_filament_surface_indentation" ||
opt_key == "mixed_filament_region_collapse" || opt_key == "mixed_filament_region_collapse" ||
opt_key == "dithering_z_step_size" || opt_key == "dithering_z_step_size" ||
@@ -1820,6 +1824,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if(m_active_page) if(m_active_page)
m_active_page->update_visibility(m_mode, true); m_active_page->update_visibility(m_mode, true);
m_page_view->GetParent()->Layout(); m_page_view->GetParent()->Layout();
if (refresh_mixed_filament_panel && wxGetApp().plater() != nullptr)
wxGetApp().sidebar().update_mixed_filament_panel(false);
} }
void Tab::show_timelapse_warning_dialog() { void Tab::show_timelapse_warning_dialog() {
@@ -2543,6 +2550,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_advanced_dithering");
optgroup->append_single_option_line("mixed_filament_pointillism_pixel_size"); 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_pointillism_line_gap");
optgroup->append_single_option_line("mixed_filament_component_bias_enabled");
optgroup->append_single_option_line("mixed_filament_surface_indentation"); optgroup->append_single_option_line("mixed_filament_surface_indentation");
optgroup->append_single_option_line("mixed_filament_region_collapse"); optgroup->append_single_option_line("mixed_filament_region_collapse");
optgroup->append_single_option_line("dithering_z_step_size"); optgroup->append_single_option_line("dithering_z_step_size");
+87 -7
View File
@@ -124,7 +124,7 @@ TEST_CASE("Mixed filament remap keeps later painted colors stable when an earlie
bundle.update_multi_material_filament_presets(); bundle.update_multi_material_filament_presets();
auto &mixed = bundle.mixed_filaments.mixed_filaments(); auto &mixed = bundle.mixed_filaments.mixed_filaments();
REQUIRE(mixed.size() >= 10); REQUIRE(mixed.size() >= 6);
const uint64_t stable_id_6 = mixed[1].stable_id; const uint64_t stable_id_6 = mixed[1].stable_id;
const uint64_t stable_id_7 = mixed[2].stable_id; const uint64_t stable_id_7 = mixed[2].stable_id;
@@ -137,7 +137,7 @@ TEST_CASE("Mixed filament remap keeps later painted colors stable when an earlie
bundle.update_mixed_filament_id_remap(old_mixed, 4, 4); bundle.update_mixed_filament_id_remap(old_mixed, 4, 4);
const std::vector<unsigned int> remap = bundle.consume_last_filament_id_remap(); const std::vector<unsigned int> remap = bundle.consume_last_filament_id_remap();
REQUIRE(remap.size() >= 15); REQUIRE(remap.size() >= 11);
CHECK(remap[6] == virtual_id_for_stable_id(mixed, 4, stable_id_6)); CHECK(remap[6] == virtual_id_for_stable_id(mixed, 4, stable_id_6));
CHECK(remap[7] == virtual_id_for_stable_id(mixed, 4, stable_id_7)); CHECK(remap[7] == virtual_id_for_stable_id(mixed, 4, stable_id_7));
CHECK(remap[8] == virtual_id_for_stable_id(mixed, 4, stable_id_8)); CHECK(remap[8] == virtual_id_for_stable_id(mixed, 4, stable_id_8));
@@ -164,7 +164,7 @@ TEST_CASE("Mixed filament grouped manual patterns normalize and round-trip", "[M
CHECK(loaded.mixed_filaments().front().mix_b_percent == 13); CHECK(loaded.mixed_filaments().front().mix_b_percent == 13);
} }
TEST_CASE("Mixed filament component surface offsets round-trip and follow the active layer component", "[MixedFilament]") TEST_CASE("Mixed filament component surface offsets round-trip and bias the second layer component", "[MixedFilament]")
{ {
const std::vector<std::string> colors = {"#FF0000", "#FFFF00"}; const std::vector<std::string> colors = {"#FF0000", "#FFFF00"};
@@ -189,18 +189,98 @@ TEST_CASE("Mixed filament component surface offsets round-trip and follow the ac
const MixedFilament &loaded_row = loaded.mixed_filaments().front(); const MixedFilament &loaded_row = loaded.mixed_filaments().front();
CHECK(loaded_row.component_a_surface_offset == Approx(0.02f)); CHECK(loaded_row.component_a_surface_offset == Approx(0.02f));
CHECK(loaded_row.component_b_surface_offset == Approx(-0.01f)); CHECK(loaded_row.component_b_surface_offset == Approx(-0.01f));
CHECK(loaded.component_surface_offset(3, 2, 0) == Approx(0.02f)); CHECK(loaded.component_surface_offset(3, 2, 0) == Approx(0.01f));
CHECK(loaded.component_surface_offset(3, 2, 1) == Approx(-0.01f)); CHECK(loaded.component_surface_offset(3, 2, 1) == Approx(0.0f));
} }
TEST_CASE("Mixed filament apparent mix percent shifts toward the less-contracted component", "[MixedFilament]") TEST_CASE("Mixed filament apparent mix percent follows the signed bias target", "[MixedFilament]")
{ {
CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.02f, 0.00f, 0.4f) == 55); CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.00f, 0.00f, 0.4f) == 50);
CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.00f, 0.02f, 0.4f) == 45); CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.00f, 0.02f, 0.4f) == 45);
CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.02f, 0.00f, 0.4f) == 55);
CHECK(MixedFilamentManager::apparent_mix_b_percent(50, -0.02f, 0.00f, 0.4f) == 45); CHECK(MixedFilamentManager::apparent_mix_b_percent(50, -0.02f, 0.00f, 0.4f) == 45);
CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.00f, -0.02f, 0.4f) == 55); CHECK(MixedFilamentManager::apparent_mix_b_percent(50, 0.00f, -0.02f, 0.4f) == 55);
} }
TEST_CASE("Mixed filament bias helper maps signed bias to a one-sided safe offset pair", "[MixedFilament]")
{
const auto [offset_a, offset_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(0.06f, 0.4f);
CHECK(offset_a == Approx(0.0f));
CHECK(offset_b == Approx(0.06f));
CHECK(MixedFilamentManager::bias_ui_value_from_surface_offsets(offset_a, offset_b, 0.4f) == Approx(0.06f));
CHECK(MixedFilamentManager::bias_ui_value_from_surface_offsets(0.02f, 0.0f, 0.4f) == Approx(-0.02f));
CHECK(MixedFilamentManager::bias_ui_value_from_surface_offsets(-0.02f, 0.0f, 0.4f) == Approx(0.02f));
const auto [negative_a, negative_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(-0.06f, 0.4f);
CHECK(negative_a == Approx(0.06f));
CHECK(negative_b == Approx(0.0f));
const auto [unclamped_a, unclamped_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(0.30f, 0.4f);
CHECK(unclamped_a == Approx(0.0f));
CHECK(unclamped_b == Approx(0.30f));
const auto [unclamped_negative_a, unclamped_negative_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(-0.30f, 0.4f);
CHECK(unclamped_negative_a == Approx(0.30f));
CHECK(unclamped_negative_b == Approx(0.0f));
const auto [clamped_a, clamped_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(0.40f, 0.4f);
CHECK(clamped_a == Approx(0.0f));
CHECK(clamped_b == Approx(0.35f));
const auto [clamped_negative_a, clamped_negative_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(-0.40f, 0.4f);
CHECK(clamped_negative_a == Approx(0.35f));
CHECK(clamped_negative_b == Approx(0.0f));
}
TEST_CASE("Mixed filament component surface offsets follow the signed bias target across alternating layers", "[MixedFilament]")
{
const std::vector<std::string> colors = {"#FF0000", "#FFFF00"};
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.clear();
row.distribution_mode = int(MixedFilament::Simple);
row.ratio_a = 1;
row.ratio_b = 1;
{
const auto [offset_a, offset_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(0.05f, 0.4f);
row.component_a_surface_offset = offset_a;
row.component_b_surface_offset = offset_b;
CHECK(mgr.component_surface_offset(3, 2, 0) == Approx(0.0f));
CHECK(mgr.component_surface_offset(3, 2, 1) == Approx(0.05f));
CHECK(mgr.component_surface_offset(3, 2, 2) == Approx(0.0f));
CHECK(mgr.component_surface_offset(3, 2, 3) == Approx(0.05f));
}
{
row.component_a_surface_offset = 0.05f;
row.component_b_surface_offset = 0.0f;
CHECK(mgr.component_surface_offset(3, 2, 0) == Approx(0.05f));
CHECK(mgr.component_surface_offset(3, 2, 1) == Approx(0.0f));
CHECK(mgr.component_surface_offset(3, 2, 2) == Approx(0.05f));
CHECK(mgr.component_surface_offset(3, 2, 3) == Approx(0.0f));
}
{
const auto [offset_a, offset_b] = MixedFilamentManager::surface_offset_pair_from_signed_bias(-0.05f, 0.4f);
row.component_a_surface_offset = offset_a;
row.component_b_surface_offset = offset_b;
CHECK(mgr.component_surface_offset(3, 2, 0) == Approx(0.05f));
CHECK(mgr.component_surface_offset(3, 2, 1) == Approx(0.0f));
CHECK(mgr.component_surface_offset(3, 2, 2) == Approx(0.05f));
CHECK(mgr.component_surface_offset(3, 2, 3) == Approx(0.0f));
}
}
TEST_CASE("Mixed filament auto generation can be disabled without dropping custom rows", "[MixedFilament]") TEST_CASE("Mixed filament auto generation can be disabled without dropping custom rows", "[MixedFilament]")
{ {
const std::vector<std::string> colors = {"#FF0000", "#00FF00", "#0000FF"}; const std::vector<std::string> colors = {"#FF0000", "#00FF00", "#0000FF"};
+3 -3
View File
@@ -10,8 +10,8 @@ endif()
if(NOT DEFINED BBL_INTERNAL_TESTING) if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0") set(BBL_INTERNAL_TESTING "0")
endif() endif()
set(Snapmaker_VERSION "0.9.5") set(Snapmaker_VERSION "0.9.51")
set(FULLSPECTRUM_VERSION "0.9.5") set(FULLSPECTRUM_VERSION "0.9.51")
set(MIN_FIRM_VER "1.0.0") set(MIN_FIRM_VER "1.0.0")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
Snapmaker_VERSION_MATCH ${Snapmaker_VERSION}) Snapmaker_VERSION_MATCH ${Snapmaker_VERSION})
@@ -19,7 +19,7 @@ set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})
set(ORCA_VERSION_MINOR ${CMAKE_MATCH_2}) set(ORCA_VERSION_MINOR ${CMAKE_MATCH_2})
set(ORCA_VERSION_PATCH ${CMAKE_MATCH_3}) set(ORCA_VERSION_PATCH ${CMAKE_MATCH_3})
set(SLIC3R_VERSION "01.10.01.50") set(SLIC3R_VERSION "01.10.01.51")
if (NOT DEFINED ORCA_UPDATER_SIG_KEY) if (NOT DEFINED ORCA_UPDATER_SIG_KEY)
set(ORCA_UPDATER_SIG_KEY "" CACHE STRING "Base64url encoded updater signature key") set(ORCA_UPDATER_SIG_KEY "" CACHE STRING "Base64url encoded updater signature key")