diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 961ed59d2b..eeb56d6e11 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3575,6 +3575,63 @@ unsigned int PresetBundle::sync_ams_list(std::vector("filament_colour_type"); ConfigOptionInts * filament_map = project_config.option("filament_map"); ConfigOptionInts * filament_volume_map = project_config.option("filament_volume_map"); + + // Snapshot and temporarily strip mixed filament slots so AMS sync operates on physical + // filaments only. A mixed slot is virtual and has no tray to sync against; leaving it in + // would let AMS mapping overwrite it and would break the physical-first slot ordering the + // rest of the feature relies on. The slots are re-appended verbatim after the sync. + struct MixedSlotSnapshot { + std::string preset; + std::string color; + std::string color_type; + std::string mixed_components; + std::string mixed_sublayer_ratios; + bool mixed_gradient = false; + std::string mixed_gradient_range; + std::string mixed_gradient_curve; + bool mixed_gradient_per_part = false; + }; + std::vector mixed_snapshots; + auto* is_mixed_opt = project_config.option("filament_is_mixed"); + auto* mixed_comp_opt = project_config.option("filament_mixed_components"); + auto* mixed_ratios_opt = project_config.option("filament_mixed_sublayer_ratios"); + auto* mixed_gradient_opt = project_config.option("filament_mixed_gradient"); + auto* mixed_grad_range_opt = project_config.option("filament_mixed_gradient_range"); + auto* mixed_grad_curve_opt = project_config.option("filament_mixed_gradient_curve"); + auto* mixed_per_part_opt = project_config.option("filament_mixed_gradient_per_part"); + if (is_mixed_opt) { + for (size_t i = 0; i < is_mixed_opt->values.size() && i < this->filament_presets.size(); ++i) { + if (!is_mixed_opt->values[i]) + continue; + MixedSlotSnapshot snap; + snap.preset = this->filament_presets[i]; + snap.color = (i < filament_color->values.size()) ? filament_color->values[i] : ""; + snap.color_type = (i < filament_color_type->values.size()) ? filament_color_type->values[i] : ""; + if (mixed_comp_opt && i < mixed_comp_opt->values.size()) snap.mixed_components = mixed_comp_opt->values[i]; + if (mixed_ratios_opt && i < mixed_ratios_opt->values.size()) snap.mixed_sublayer_ratios = mixed_ratios_opt->values[i]; + if (mixed_gradient_opt && i < mixed_gradient_opt->values.size()) snap.mixed_gradient = mixed_gradient_opt->values[i]; + if (mixed_grad_range_opt && i < mixed_grad_range_opt->values.size()) snap.mixed_gradient_range = mixed_grad_range_opt->values[i]; + if (mixed_grad_curve_opt && i < mixed_grad_curve_opt->values.size()) snap.mixed_gradient_curve = mixed_grad_curve_opt->values[i]; + if (mixed_per_part_opt && i < mixed_per_part_opt->values.size()) snap.mixed_gradient_per_part = mixed_per_part_opt->values[i]; + mixed_snapshots.push_back(snap); + } + if (!mixed_snapshots.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": stripping " << mixed_snapshots.size() << " mixed filament slot(s) before AMS sync"; + size_t phys_count = this->filament_presets.size() - mixed_snapshots.size(); + this->filament_presets.resize(phys_count); + filament_color->values.resize(phys_count); + filament_color_type->values.resize(phys_count); + filament_map->values.resize(phys_count, 1); + is_mixed_opt->values.resize(phys_count); + if (mixed_comp_opt) mixed_comp_opt->values.resize(phys_count); + if (mixed_ratios_opt) mixed_ratios_opt->values.resize(phys_count); + if (mixed_gradient_opt) mixed_gradient_opt->values.resize(phys_count); + if (mixed_grad_range_opt) mixed_grad_range_opt->values.resize(phys_count); + if (mixed_grad_curve_opt) mixed_grad_curve_opt->values.resize(phys_count); + if (mixed_per_part_opt) mixed_per_part_opt->values.resize(phys_count); + } + } + if (color_only) { auto get_map_index = [&ams_infos](const std::vector &infos, const AMSMapInfo &temp) { for (int i = 0; i < infos.size(); i++) { @@ -3830,6 +3887,34 @@ unsigned int PresetBundle::sync_ams_list(std::vectorvalue > filament_color_type->values.size()) support_interface_filament_opt->value = 0; } + // Re-append mixed filament slots that were stripped before AMS sync + if (!mixed_snapshots.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": re-appending " << mixed_snapshots.size() << " mixed filament slot(s) after AMS sync"; + size_t new_phys_count = this->filament_presets.size(); + if (is_mixed_opt) is_mixed_opt->values.resize(new_phys_count, (unsigned char)false); + if (mixed_comp_opt) mixed_comp_opt->values.resize(new_phys_count); + if (mixed_ratios_opt) mixed_ratios_opt->values.resize(new_phys_count); + if (mixed_gradient_opt) mixed_gradient_opt->values.resize(new_phys_count, (unsigned char)false); + if (mixed_grad_range_opt) mixed_grad_range_opt->values.resize(new_phys_count); + if (mixed_grad_curve_opt) mixed_grad_curve_opt->values.resize(new_phys_count); + if (mixed_per_part_opt) mixed_per_part_opt->values.resize(new_phys_count, (unsigned char)false); + + for (auto& snap : mixed_snapshots) { + this->filament_presets.push_back(snap.preset); + filament_color->values.push_back(snap.color); + filament_color_type->values.push_back(snap.color_type); + ams_multi_color_filment.push_back({snap.color}); + filament_map->values.push_back(1); + if (is_mixed_opt) is_mixed_opt->values.push_back((unsigned char)true); + if (mixed_comp_opt) mixed_comp_opt->values.push_back(snap.mixed_components); + if (mixed_ratios_opt) mixed_ratios_opt->values.push_back(snap.mixed_sublayer_ratios); + if (mixed_gradient_opt) mixed_gradient_opt->values.push_back((unsigned char)snap.mixed_gradient); + if (mixed_grad_range_opt) mixed_grad_range_opt->values.push_back(snap.mixed_gradient_range); + if (mixed_grad_curve_opt) mixed_grad_curve_opt->values.push_back(snap.mixed_gradient_curve); + if (mixed_per_part_opt) mixed_per_part_opt->values.push_back((unsigned char)snap.mixed_gradient_per_part); + } + } + // Update ams_multi_color_filment update_filament_multi_color(); update_multi_material_filament_presets(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 967e02a907..a29ff1a9d0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -8911,7 +8911,10 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICE_FAILED; } else { - if ((!is_empty && !can_slice) || (plate_list.get_plate(i)->has_printable_instances() && !plate_list.get_plate(i)->can_slice())) + // A plate using a mixed filament whose components are broken cannot be sliced, + // so surface that on the plate toolbar the same way an unsliceable plate is. + if ((!is_empty && !can_slice) || (plate_list.get_plate(i)->has_printable_instances() && !plate_list.get_plate(i)->can_slice()) + || wxGetApp().plater()->sidebar().has_broken_mixed_filament(plate_list.get_plate(i))) m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICE_FAILED; else { if (plate_list.get_plate(i)->get_slicing_percent() < 0.0f) @@ -9707,6 +9710,10 @@ void GLCanvas3D::_render_paint_toolbar() const bool disabled = !wxGetApp().plater()->can_fillcolor(); ColorRGBA rgba; + // Gradient mixed filaments fade between two colours over Z, so their swatch is drawn as a + // two-tone fade rather than the single blended colour in `colors`. + auto gradient_info = wxGetApp().plater()->get_filament_gradient_info(); + for (int i = 0; i < extruder_num; i++) { if (i > 0) ImGui::SameLine(); @@ -9720,6 +9727,16 @@ void GLCanvas3D::_render_paint_toolbar() const if (!ImGui::IsMouseHoveringRect(left_arrow_button.Min, left_arrow_button.Max) && !ImGui::IsMouseHoveringRect(right_arrow_button.Min, right_arrow_button.Max)) wxPostEvent(m_canvas, IntEvent(EVT_GLTOOLBAR_FILLCOLOR, i + 1)); } + if (i < (int) gradient_info.size() && gradient_info[i].is_gradient) { + auto to_imu32 = [](const std::array &c) -> ImU32 { + return IM_COL32(uint8_t(c[0]*255.f), uint8_t(c[1]*255.f), uint8_t(c[2]*255.f), uint8_t(c[3]*255.f)); + }; + ImVec2 r_min = ImGui::GetItemRectMin(); + ImVec2 r_max = ImGui::GetItemRectMax(); + ImU32 col_from = to_imu32(gradient_info[i].color_from); + ImU32 col_to = to_imu32(gradient_info[i].color_to); + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(r_min, r_max, col_from, col_to, col_to, col_from); + } if (ImGui::IsItemHovered() && i < 9) { if (!ImGui::IsMouseHoveringRect(left_arrow_button.Min, left_arrow_button.Max) && !ImGui::IsMouseHoveringRect(right_arrow_button.Min, right_arrow_button.Max)) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 20.0f * f_scale, 10.0f * f_scale }); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 5254492e5d..9ee74d742f 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1684,11 +1684,18 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men append_submenu(menu, sub_menu, wxID_ANY, _L("Merge with"), "", "", [filaments_cnt]() { return filaments_cnt > 1; }, m_parent); + // Decompose a target colour into a printable mix of the loaded filaments. Placed before the + // Delete entry below so Orca's "delete last" ordering is preserved (BBS appends it after). + append_menu_item( + menu, wxID_ANY, _L("Decompose Color"), "", [](wxCommandEvent&) { + plater()->sidebar().decompose_filament_color(kSidebarContextMenuFilamentId); }, "", nullptr, + []() { return plater()->sidebar().combos_filament().size() >= 2; }, m_parent); + // ORCA use delete item on end of menu to prevent accidental clicks. clicking to submenus(merge) already not allowed by OS const int delete_id = menu->FindItem(_L("Delete")); if (delete_id != wxNOT_FOUND) menu->Destroy(delete_id); - + append_menu_item( menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) { plater()->sidebar().delete_filament(-2); }, "", nullptr, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 0ee0230ea9..5ac8cb814c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -78,6 +78,14 @@ void GLGizmoMmuSegmentation::init_extruders_data() m_extruders_colors = wxGetApp().plater()->get_extruders_colors(); m_selected_extruder_idx = 0; + auto plater_grad = wxGetApp().plater()->get_filament_gradient_info(); + m_gradient_info.resize(m_extruders_colors.size()); + for (size_t i = 0; i < m_gradient_info.size() && i < plater_grad.size(); ++i) { + m_gradient_info[i].is_gradient = plater_grad[i].is_gradient; + m_gradient_info[i].color_from = plater_grad[i].color_from; + m_gradient_info[i].color_to = plater_grad[i].color_to; + } + // keep remap table consistent with current extruder count m_extruder_remap.resize(m_extruders_colors.size()); for (size_t i = 0; i < m_extruder_remap.size(); ++i) @@ -433,6 +441,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_selected_extruder_idx = extruder_idx; } + // Overlay a two-tone fade for gradient mixed filaments; a single flat colour would + // misrepresent a slot that fades between two filaments over Z. + if (extruder_idx < (int) m_gradient_info.size() && m_gradient_info[extruder_idx].is_gradient) { + auto to_imu32 = [](const std::array &c) -> ImU32 { + return IM_COL32(uint8_t(c[0]*255.f), uint8_t(c[1]*255.f), uint8_t(c[2]*255.f), uint8_t(c[3]*255.f)); + }; + ImVec2 r_min = ImGui::GetItemRectMin(); + ImVec2 r_max = ImGui::GetItemRectMax(); + ImU32 col_from = to_imu32(m_gradient_info[extruder_idx].color_from); + ImU32 col_to = to_imu32(m_gradient_info[extruder_idx].color_to); + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(r_min, r_max, col_from, col_to, col_to, col_from); + } + if (extruder_idx < 16 && ImGui::IsItemHovered()) m_imgui->tooltip(_L("Shortcut Key ") + std::to_string(extruder_idx + 1), max_tooltip_width); } // ORCA: Remap filaments section (Border only, Title in border). diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 55308bffa9..e5448c2dcb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -79,6 +79,14 @@ public: // will be also extended to support additional states, requiring at least one state to remain free out of 19 states. static const constexpr size_t EXTRUDERS_LIMIT = 16; + // Endpoint colours for gradient mixed filaments, mirrored from Plater so the extruder + // swatches below can be drawn as a two-tone fade instead of a single blended colour. + struct GradientInfo { + bool is_gradient = false; + std::array color_from = {0.5f, 0.5f, 0.5f, 1.0f}; + std::array color_to = {0.5f, 0.5f, 0.5f, 1.0f}; + }; + const float get_cursor_radius_min() const override { return CursorRadiusMin; } // BBS @@ -116,6 +124,7 @@ protected: // Filament remap feature std::vector m_extruder_remap; // index → target extruder index + std::vector m_gradient_info; // per-slot gradient endpoints, empty entries for plain filaments // ORCA: Cache used filaments to filter UI std::set m_used_filaments; // Set of used filament indices (cached) diff --git a/src/slic3r/GUI/PlateSettingsDialog.cpp b/src/slic3r/GUI/PlateSettingsDialog.cpp index ea24d646c3..e7f1d926d9 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.cpp +++ b/src/slic3r/GUI/PlateSettingsDialog.cpp @@ -472,6 +472,32 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, const wxString& title m_sizer_main->AddSpacer(FromDIP(5)); m_sizer_main->Add(m_other_layers_seq_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(30)); + // A mixed-color slot resolves to a different physical filament per layer, so a user-defined + // filament order cannot be honoured. Disable the choice and say why. BBS puts this warning + // inside its button sizer; Orca builds the buttons with DialogButtons, so it gets its own row. + { + auto &proj_cfg = wxGetApp().preset_bundle->project_config; + auto *is_mixed_opt = proj_cfg.option("filament_is_mixed"); + if (is_mixed_opt && Slic3r::has_any_mixed_filament(is_mixed_opt->values)) { + m_first_layer_print_seq_choice->Enable(false); + m_other_layers_seq_panel->enable_seq_choice(false); + + auto *warn_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *warn_icon = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("warning", this, 16), + wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); + auto *warn_text = new wxStaticText(this, wxID_ANY, + _L("The filament list contains mixed filaments. Custom filament sequence will not take effect.")); + warn_text->SetForegroundColour(wxColour(255, 111, 0)); + warn_text->SetFont(Label::Body_12); + warn_text->Wrap(FromDIP(300)); + + warn_sizer->Add(warn_icon, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5)); + warn_sizer->Add(warn_text, 1, wxALIGN_CENTER_VERTICAL, 0); + m_sizer_main->AddSpacer(FromDIP(5)); + m_sizer_main->Add(warn_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(30)); + } + } + auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"}); dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](auto& e) { diff --git a/src/slic3r/GUI/PlateSettingsDialog.hpp b/src/slic3r/GUI/PlateSettingsDialog.hpp index 1e61b0a708..b94739348f 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.hpp +++ b/src/slic3r/GUI/PlateSettingsDialog.hpp @@ -62,6 +62,9 @@ public: int get_layers_print_seq_choice() { return m_other_layer_print_seq_choice->GetSelection(); }; std::vector get_layers_print_seq_infos() { return m_layer_seq_infos; } + // Lets callers grey out the sequence choice (e.g. when a mixed filament makes a + // user-defined filament order impossible). + void enable_seq_choice(bool enable) { m_other_layer_print_seq_choice->Enable(enable); } protected: void append_layer(const LayerSeqInfo* layer_info = nullptr); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 676d29b961..2fc8ca482d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5355,9 +5355,16 @@ void Sidebar::delete_filament(size_t filament_id, int replace_filament_id) { p->editing_filament = -1; } + // update_num_filaments() shrinks filament_is_mixed along with the other per-filament arrays, + // so snapshot it first — the paint cleanup below needs to know which slots were mixed + // *before* the delete to avoid discarding assignments to still-valid mixed slots. + std::vector is_mixed_snapshot; + if (auto* opt = wxGetApp().preset_bundle->project_config.option("filament_is_mixed")) + is_mixed_snapshot = opt->values; + wxGetApp().preset_bundle->update_num_filaments(filament_id); wxGetApp().plater()->get_partplate_list().on_filament_deleted(filament_count, filament_id); - wxGetApp().plater()->on_filaments_delete(filament_count, filament_id, replace_filament_id > (int)filament_id ? (replace_filament_id - 1) : replace_filament_id); + wxGetApp().plater()->on_filaments_delete(filament_count, filament_id, replace_filament_id > (int)filament_id ? (replace_filament_id - 1) : replace_filament_id, is_mixed_snapshot); wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); @@ -5374,6 +5381,36 @@ void Sidebar::delete_filament(size_t filament_id, int replace_filament_id) { void Sidebar::change_filament(size_t from_id, size_t to_id) { + // Merging a physical filament into a mixed one that lists it as a component would delete + // the very filament the mix depends on, leaving it broken. Warn before doing so. + auto& pb = *wxGetApp().preset_bundle; + bool from_is_physical = !pb.is_mixed_filament(from_id); + bool to_is_mixed = pb.is_mixed_filament(to_id); + + if (from_is_physical && to_is_mixed) { + auto* comp_opt = pb.project_config.option("filament_mixed_components"); + if (comp_opt && to_id < comp_opt->values.size()) { + auto comps = Slic3r::parse_mixed_components(comp_opt->values[to_id]); + unsigned int from_1based = (unsigned int)from_id + 1; + bool target_uses_source = false; + for (unsigned int c : comps) { + if (c == from_1based) { + target_uses_source = true; + break; + } + } + if (target_uses_source) { + int ret = wxMessageBox( + _L("The target mixed filament uses this physical filament as a component. " + "Merging will remove this physical filament and may invalidate the mixed filament. Continue?"), + _L("Warning"), + wxOK | wxCANCEL | wxICON_WARNING); + if (ret != wxOK) + return; + } + } + } + delete_filament(from_id, int(to_id)); } @@ -9744,7 +9781,11 @@ void Plater::priv::object_list_changed() // BBS //sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances()); - bool can_slice = !model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances(); + // A mixed filament with deleted or type-mismatched components cannot be resolved at slicing + // time, so block the slice buttons the same way MainFrame::get_enable_slice_status() does. + bool mixed_broken = sidebar->has_broken_mixed_filament(); + bool can_slice = !model.objects.empty() && !export_in_progress && model_fits && part_plate->has_printable_instances() + && !mixed_broken; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": can_slice %1%, model_fits= %2%, export_in_progress %3%, has_printable_instances %4% ")%can_slice %model_fits %export_in_progress %part_plate->has_printable_instances(); main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, can_slice); @@ -13963,6 +14004,25 @@ bool Plater::priv::can_layers_editing() const void Plater::priv::on_action_layersediting(SimpleEvent&) { + // Sub-layer splitting divides each layer by the mix ratio, so an adaptive layer profile makes + // those sub-layer heights vary and degrades the blend. ConfigManipulation warns when the + // option is switched on with a variable profile already present; this is the other direction, + // warning when variable layer editing is switched on while the option is active. Both honour + // the same do-not-show-again flag. + if (!view3D->is_layers_editing_enabled()) { + const auto& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + if (print_config.opt_bool("enable_mixed_color_sublayer")) { + if (wxGetApp().app_config->get("no_warn_mixed_sublayer_variable_layer") != "1") { + MessageDialog dlg(q, + _L("Using variable layer height together with mixed color sublayer may result in poor color mixing quality."), + _L("Warning"), wxICON_WARNING | wxOK); + dlg.show_dsa_button(); + dlg.ShowModal(); + if (dlg.get_checkbox_state()) + wxGetApp().app_config->set("no_warn_mixed_sublayer_variable_layer", "1"); + } + } + } view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled()); } @@ -19385,7 +19445,7 @@ void Plater::on_filament_count_change(size_t num_filaments) } } -void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int replace_filament_id) +void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int replace_filament_id, const std::vector& is_mixed_before_delete) { // only update elements in plater update_filament_colors_in_full_config(); @@ -19399,9 +19459,15 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r }*/ // update mmu info + // A volume assigned to a mixed slot legitimately sits past the physical filament count, so + // the paint cleanup must know which slots were mixed. Callers that already shrank the arrays + // pass the pre-delete flags; otherwise read the current ones. + const auto &is_mixed = is_mixed_before_delete.empty() + ? wxGetApp().preset_bundle->project_config.option("filament_is_mixed")->values + : is_mixed_before_delete; for (ModelObject *mo : wxGetApp().model().objects) { for (ModelVolume *mv : mo->volumes) { - mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1, replace_filament_id + 1); // this function is 1 base + mv->update_extruder_count_when_delete_filament(num_filaments, filament_id + 1, replace_filament_id + 1, is_mixed); // this function is 1 base } } @@ -19740,6 +19806,63 @@ std::vector Plater::get_extruder_colors_from_plater_config(const GC } } +namespace { + +// A gradient mixed filament fades between its two components over Z, so the UI shows it as a +// two-tone swatch rather than one blended colour. Resolve each slot to its from/to endpoint +// colours; non-gradient slots are left untouched. +struct MixedGradientSlot { + bool is_gradient = false; + std::string color_from; + std::string color_to; +}; + +std::vector parse_mixed_gradient_slots(const Slic3r::DynamicPrintConfig& config, size_t slot_count) +{ + std::vector result(slot_count); + const auto* is_mixed = config.option("filament_is_mixed"); + const auto* mixed_grad = config.option("filament_mixed_gradient"); + const auto* mixed_comp = config.option("filament_mixed_components"); + const auto* grad_range = config.option("filament_mixed_gradient_range"); + const auto* fil_colour = config.option("filament_colour"); + if (!is_mixed || !mixed_grad || !mixed_comp || !fil_colour) return result; + + for (size_t i = 0; i < slot_count && i < is_mixed->values.size(); ++i) { + if (!is_mixed->values[i]) continue; + if (i >= mixed_grad->values.size() || !mixed_grad->values[i]) continue; + if (i >= mixed_comp->values.size()) continue; + + std::vector comp_ids; + std::istringstream iss(mixed_comp->values[i]); + std::string tok; + while (std::getline(iss, tok, ',')) { + unsigned int v = 0; + if (std::sscanf(tok.c_str(), "%u", &v) == 1) + comp_ids.push_back(v); + } + if (comp_ids.size() != 2) continue; + + int direction = 0; + if (grad_range && i < grad_range->values.size()) { + CNumericLocalesSetter c_locale_setter; + float v0 = 0, v1 = 0; + if (std::sscanf(grad_range->values[i].c_str(), "%f,%f", &v0, &v1) == 2) + direction = (v0 > v1) ? 0 : 1; + } + + unsigned int from_id = (direction == 0) ? comp_ids[0] : comp_ids[1]; + unsigned int to_id = (direction == 0) ? comp_ids[1] : comp_ids[0]; + result[i].is_gradient = true; + result[i].color_from = (from_id >= 1 && from_id <= fil_colour->values.size()) + ? fil_colour->values[from_id - 1] : "#D9D9D9"; + result[i].color_to = (to_id >= 1 && to_id <= fil_colour->values.size()) + ? fil_colour->values[to_id - 1] : "#D9D9D9"; + } + return result; +} + +} // anonymous namespace + std::vector Plater::get_filament_colors_render_info() const { const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config; @@ -19747,6 +19870,13 @@ std::vector Plater::get_filament_colors_render_info() const if (!config->has("filament_multi_colour")) return color_packs; color_packs = (config->option("filament_multi_colour"))->values; + + auto slots = parse_mixed_gradient_slots(*config, color_packs.size()); + for (size_t i = 0; i < color_packs.size(); ++i) { + if (slots[i].is_gradient) + color_packs[i] = slots[i].color_from + " " + slots[i].color_to; + } + return color_packs; } @@ -19757,9 +19887,37 @@ std::vector Plater::get_filament_color_render_type() const if (!config->has("filament_colour_type")) return ctype; ctype = (config->option("filament_colour_type"))->values; + + auto slots = parse_mixed_gradient_slots(*config, ctype.size()); + while (ctype.size() < slots.size()) ctype.push_back("1"); + for (size_t i = 0; i < ctype.size() && i < slots.size(); ++i) { + if (slots[i].is_gradient) + ctype[i] = "0"; + } + return ctype; } +std::vector Plater::get_filament_gradient_info() const +{ + const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->project_config; + size_t n = get_extruder_colors_from_plater_config().size(); + std::vector info(n); + + auto slots = parse_mixed_gradient_slots(*config, n); + unsigned char rgba[4] = {}; + for (size_t i = 0; i < n; ++i) { + if (!slots[i].is_gradient) continue; + info[i].is_gradient = true; + Slic3r::GUI::BitmapCache::parse_color4(slots[i].color_from, rgba); + info[i].color_from = {rgba[0] / 255.f, rgba[1] / 255.f, rgba[2] / 255.f, rgba[3] / 255.f}; + Slic3r::GUI::BitmapCache::parse_color4(slots[i].color_to, rgba); + info[i].color_to = {rgba[0] / 255.f, rgba[1] / 255.f, rgba[2] / 255.f, rgba[3] / 255.f}; + } + + return info; +} + /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 49bc247c59..147f61bed6 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -591,7 +591,7 @@ public: void on_filament_change(size_t filament_idx); void on_filament_count_change(size_t extruders_count); - void on_filaments_delete(size_t extruders_count, size_t filament_id, int replace_filament_id = -1); + void on_filaments_delete(size_t extruders_count, size_t filament_id, int replace_filament_id = -1, const std::vector& is_mixed_before_delete = {}); std::vector get_extruders_colors(); // BBS void on_bed_type_change(BedType bed_type); @@ -606,6 +606,15 @@ public: std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; std::vector get_filament_colors_render_info() const; std::vector get_filament_color_render_type() const; + + // Endpoint colours for gradient mixed filaments, so the 3D scene and the paint gizmo can + // draw a two-tone swatch. is_gradient is false for every ordinary filament slot. + struct FilamentGradientInfo { + bool is_gradient = false; + std::array color_from = {0.5f, 0.5f, 0.5f, 1.0f}; + std::array color_to = {0.5f, 0.5f, 0.5f, 1.0f}; + }; + std::vector get_filament_gradient_info() const; std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; void set_global_filament_map_mode(FilamentMapMode mode); diff --git a/src/slic3r/GUI/SyncAmsInfoDialog.cpp b/src/slic3r/GUI/SyncAmsInfoDialog.cpp index ea67ad5b31..3524d89c74 100644 --- a/src/slic3r/GUI/SyncAmsInfoDialog.cpp +++ b/src/slic3r/GUI/SyncAmsInfoDialog.cpp @@ -2575,6 +2575,10 @@ void SyncAmsInfoDialog::reset_and_sync_ams_list() m_materialList.clear(); m_filaments.clear(); + // Mixed-color slots are virtual: they never occupy a tray, so they must not appear as + // AMS sync targets. Look the flags up once and skip those slots in the loop below. + auto* is_mixed_opt = preset_bundle->project_config.option("filament_is_mixed"); + bool use_double_extruder = get_is_double_extruder(); if (use_double_extruder) { const auto &project_config = preset_bundle->project_config; @@ -2592,6 +2596,8 @@ void SyncAmsInfoDialog::reset_and_sync_ams_list() auto colour_rgb = wxColour((int) rgb[0], (int) rgb[1], (int) rgb[2], (int) rgb[3]); if (extruder >= materials.size() || extruder < 0 || extruder >= display_materials.size()) continue; + if (is_mixed_opt && extruder < (int) is_mixed_opt->values.size() && is_mixed_opt->values[extruder]) + continue; if (contronal_index % SYNC_FLEX_GRID_COL == 0) { wxBoxSizer *ams_tip_sizer = new wxBoxSizer(wxVERTICAL); @@ -2793,6 +2799,10 @@ void SyncAmsInfoDialog::generate_override_fix_ams_list() m_fix_materialList.clear(); m_fix_filaments.clear(); + // Mixed-color slots are virtual: they never occupy a tray, so they must not appear as + // AMS sync targets. Look the flags up once and skip those slots in the loop below. + auto* is_mixed_opt = preset_bundle->project_config.option("filament_is_mixed"); + bool use_double_extruder = get_is_double_extruder(); if (use_double_extruder) { const auto &project_config = preset_bundle->project_config; @@ -2810,6 +2820,8 @@ void SyncAmsInfoDialog::generate_override_fix_ams_list() auto colour_rgb = wxColour((int) rgb[0], (int) rgb[1], (int) rgb[2], (int) rgb[3]); if (extruder >= extruders.size() || extruder < 0 || extruder >= m_ams_combo_info.ams_filament_colors.size()) continue; + if (is_mixed_opt && extruder < (int) is_mixed_opt->values.size() && is_mixed_opt->values[extruder]) + continue; if (contronal_index % SYNC_FLEX_GRID_COL == 0) { wxBoxSizer *ams_tip_sizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 42796dd3e5..045ce9c43c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2630,6 +2630,7 @@ void TabPrint::build() auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); optgroup->append_single_option_line("layer_height","quality_settings_layer_height"); optgroup->append_single_option_line("initial_layer_print_height","quality_settings_layer_height"); + optgroup->append_single_option_line("enable_mixed_color_sublayer"); optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); optgroup->append_single_option_line("line_width","quality_settings_line_width");