diff --git a/dumpinfo.cs b/dumpinfo.cs new file mode 100644 index 0000000000..5cd213a6f3 --- /dev/null +++ b/dumpinfo.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +class DumpInfo +{ + const int MiniDumpExceptionStream = 6; + const int MiniDumpModuleListStream = 4; + + [StructLayout(LayoutKind.Sequential)] + struct MINIDUMP_LOCATION_DESCRIPTOR { public uint DataSize; public uint Rva; } + + [StructLayout(LayoutKind.Sequential)] + struct MINIDUMP_DIRECTORY { public uint StreamType; public MINIDUMP_LOCATION_DESCRIPTOR Location; } + + [StructLayout(LayoutKind.Sequential)] + struct MINIDUMP_EXCEPTION + { + public uint ExceptionCode; + public uint ExceptionFlags; + public ulong ExceptionRecord; + public ulong ExceptionAddress; + public uint NumberParameters; + public uint __unusedAlignment; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] public ulong[] ExceptionInformation; + } + + [StructLayout(LayoutKind.Sequential)] + struct MINIDUMP_EXCEPTION_STREAM + { + public uint ThreadId; + public uint __alignment; + public MINIDUMP_EXCEPTION ExceptionRecord; + public MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct MINIDUMP_STRING_HEADER { public uint Length; } + + [StructLayout(LayoutKind.Sequential)] + struct VS_FIXEDFILEINFO + { + public uint dwSignature; public uint dwStrucVersion; public uint dwFileVersionMS; public uint dwFileVersionLS; + public uint dwProductVersionMS; public uint dwProductVersionLS; public uint dwFileFlagsMask; public uint dwFileFlags; + public uint dwFileOS; public uint dwFileType; public uint dwFileSubtype; public uint dwFileDateMS; public uint dwFileDateLS; + } + + [StructLayout(LayoutKind.Sequential)] + struct MINIDUMP_MODULE + { + public ulong BaseOfImage; + public uint SizeOfImage; + public uint CheckSum; + public uint TimeDateStamp; + public uint ModuleNameRva; + public VS_FIXEDFILEINFO VersionInfo; + public MINIDUMP_LOCATION_DESCRIPTOR CvRecord; + public MINIDUMP_LOCATION_DESCRIPTOR MiscRecord; + public ulong Reserved0; + public ulong Reserved1; + } + + [DllImport("dbghelp.dll", SetLastError = true)] + static extern bool MiniDumpReadDumpStream(IntPtr BaseOfDump, int StreamNumber, out IntPtr Dir, out IntPtr StreamPointer, out uint StreamSize); + + static T PtrTo(IntPtr p) where T : struct => Marshal.PtrToStructure(p); + + static string ReadMinidumpString(IntPtr basePtr, uint rva) + { + if (rva == 0) return ""; + var header = PtrTo(IntPtr.Add(basePtr, (int)rva)); + var strPtr = IntPtr.Add(basePtr, (int)rva + 4); + return Marshal.PtrToStringUni(strPtr, (int)header.Length / 2) ?? ""; + } + + static void Main(string[] args) + { + if (args.Length == 0) { Console.WriteLine("usage: DumpInfo "); return; } + var path = args[0]; + using var fs = File.OpenRead(path); + using var mm = System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(fs, null, 0, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read, null, System.IO.HandleInheritability.None, false); + using var view = mm.CreateViewAccessor(0, 0, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read); + SafeMemoryMappedViewHandle handle = view.SafeMemoryMappedViewHandle; + handle.AcquirePointer(ref bytePtr); + try { + IntPtr basePtr = (IntPtr)bytePtr; + if (MiniDumpReadDumpStream(basePtr, MiniDumpExceptionStream, out _, out var exPtr, out _)) { + var ex = PtrTo(exPtr); + Console.WriteLine($"ThreadId: {ex.ThreadId}"); + Console.WriteLine($"ExceptionCode: 0x{ex.ExceptionRecord.ExceptionCode:X8}"); + Console.WriteLine($"ExceptionAddress: 0x{ex.ExceptionRecord.ExceptionAddress:X16}"); + ulong addr = ex.ExceptionRecord.ExceptionAddress; + if (MiniDumpReadDumpStream(basePtr, MiniDumpModuleListStream, out _, out var modPtr, out _)) { + uint count = (uint)Marshal.ReadInt32(modPtr); + IntPtr cur = IntPtr.Add(modPtr, 4); + string bestName = ""; + ulong bestBase = 0; uint bestSize = 0; + for (uint i = 0; i < count; ++i) { + var mod = PtrTo(cur); + if (addr >= mod.BaseOfImage && addr < mod.BaseOfImage + mod.SizeOfImage) { + bestName = ReadMinidumpString(basePtr, mod.ModuleNameRva); + bestBase = mod.BaseOfImage; + bestSize = mod.SizeOfImage; + break; + } + cur = IntPtr.Add(cur, Marshal.SizeOf()); + } + if (bestName.Length > 0) { + Console.WriteLine($"Module: {bestName}"); + Console.WriteLine($"ModuleBase: 0x{bestBase:X16}"); + Console.WriteLine($"Offset: 0x{(addr - bestBase):X}"); + Console.WriteLine($"ModuleSize: 0x{bestSize:X}"); + } else { + Console.WriteLine("Module: "); + } + } + } else { + Console.WriteLine("MiniDumpReadDumpStream for exception stream failed"); + Console.WriteLine(Marshal.GetLastWin32Error()); + } + } + finally { + handle.ReleasePointer(); + } + } + static unsafe byte* bytePtr; +} diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index de8814679e..c209a15082 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -321,6 +321,10 @@ void AppConfig::set_defaults() set_bool("auto_calculate_when_filament_change", true); } + if (get("auto_generate_gradients").empty()) { + set_bool("auto_generate_gradients", true); + } + if (get("show_home_page").empty()) { set_bool("show_home_page", true); } diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index 99bee34b93..2dcd5d28dc 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -2,6 +2,7 @@ #include "filament_mixer.h" #include +#include #include #include #include @@ -15,6 +16,12 @@ namespace Slic3r { +namespace { + +std::atomic_bool s_mixed_filament_auto_generate_enabled { true }; + +} // namespace + static uint64_t canonical_pair_key(unsigned int a, unsigned int b) { const unsigned int lo = std::min(a, b); @@ -810,6 +817,16 @@ uint64_t MixedFilamentManager::normalize_stable_id(uint64_t stable_id) return stable_id; } +void MixedFilamentManager::set_auto_generate_enabled(bool enabled) +{ + s_mixed_filament_auto_generate_enabled.store(enabled, std::memory_order_relaxed); +} + +bool MixedFilamentManager::auto_generate_enabled() +{ + return s_mixed_filament_auto_generate_enabled.load(std::memory_order_relaxed); +} + void MixedFilamentManager::auto_generate(const std::vector &filament_colours) { // Keep a copy of the old list so we can preserve user-modified ratios and @@ -818,8 +835,6 @@ void MixedFilamentManager::auto_generate(const std::vector &filamen m_mixed.clear(); const size_t n = filament_colours.size(); - if (n < 2) - return; std::vector custom_rows; custom_rows.reserve(old.size()); @@ -837,6 +852,13 @@ void MixedFilamentManager::auto_generate(const std::vector &filamen custom_rows.push_back(std::move(custom)); } + if (n < 2 || !auto_generate_enabled()) { + for (MixedFilament &mf : custom_rows) + m_mixed.push_back(std::move(mf)); + refresh_display_colors(filament_colours); + return; + } + // Generate all C(N,2) pairwise combinations. for (size_t i = 0; i < n; ++i) { for (size_t j = i + 1; j < n; ++j) { diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp index 88014006a8..167a008255 100644 --- a/src/libslic3r/MixedFilament.hpp +++ b/src/libslic3r/MixedFilament.hpp @@ -116,6 +116,9 @@ class MixedFilamentManager public: MixedFilamentManager() = default; + static void set_auto_generate_enabled(bool enabled); + static bool auto_generate_enabled(); + // ---- Auto-generation ------------------------------------------------ // Rebuild the mixed-filament list from the current set of physical diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 52c33b68c3..cc81973868 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3469,7 +3469,7 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam // Build old->new filament ID remap for painted facet data normalization. // This is needed for both deletion and addition of physical filaments so // painted mixed states keep pointing at the same virtual mixed entries. - if (old_num_filaments != num_filaments || deleting_filament) + if (old_num_filaments != num_filaments || deleting_filament || old_mixed != this->mixed_filaments.mixed_filaments()) build_filament_id_remap(old_mixed, old_num_filaments, num_filaments, deleting_filament, deleting_filament ? unsigned(to_delete_filament_id + 1) : 0u); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index cbb6a4b193..42d9f47df8 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -16,6 +16,7 @@ #include "slic3r/GUI/WCPDownloadManager.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Config/Version.hpp" +#include "libslic3r/MixedFilament.hpp" // Localization headers: include libslic3r version first so everything in this file // uses the slic3r/GUI version (the macros will take precedence over the functions). @@ -2130,6 +2131,7 @@ void GUI_App::init_app_config() } #endif // _WIN32 } + MixedFilamentManager::set_auto_generate_enabled(app_config->get_bool("auto_generate_gradients")); set_logging_level(Slic3r::level_string_to_boost(app_config->get("log_severity_level"))); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45a4ad5dec..4ec5879f42 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1352,6 +1352,7 @@ Sidebar::Sidebar(Plater *parent) return; int filament_count = p->combos_filament.size() + 1; + wxGetApp().plater()->confirm_auto_generated_gradients(filament_count); wxColour new_col = Plater::get_next_color_for_filament(); std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); @@ -6878,7 +6879,7 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) if (p->m_btn_add_color) p->m_btn_add_color->Enable(num_physical >= 2); - if (mixed.empty()) { + if (num_physical < 2) { p->m_panel_mixed_filaments_title->Hide(); p->m_panel_mixed_filaments_content->Hide(); Layout(); @@ -6900,6 +6901,27 @@ void Sidebar::update_mixed_filament_panel(bool sync_manager) auto *rows_sizer = new wxBoxSizer(wxVERTICAL); rows_scroller->SetSizer(rows_sizer); + if (mixed.empty()) { + auto *empty_label = new wxStaticText(rows_scroller, wxID_ANY, + _L("No mixed filaments yet. Use Add Gradient, Add Pattern, or Add Color to create one.")); + empty_label->SetForegroundColour(mixed_summary_fg); + empty_label->SetFont(::Label::Body_13); + empty_label->Wrap(FromDIP(360)); + rows_sizer->Add(empty_label, 0, wxALL | wxEXPAND, FromDIP(12)); + rows_scroller->Layout(); + rows_scroller->FitInside(); + const int empty_content_h = empty_label->GetBestSize().GetHeight() + FromDIP(28); + const int empty_rows_h = std::max(FromDIP(86), empty_content_h); + rows_scroller->SetMinSize(wxSize(-1, empty_rows_h)); + rows_scroller->SetMaxSize(wxSize(-1, empty_rows_h)); + if (content_sizer) + content_sizer->Add(rows_scroller, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(SidebarProps::ContentMargin())); + p->m_panel_mixed_filaments_content->Layout(); + Layout(); + refresh_model_canvas_colors(); + return; + } + auto adjust_rows_scroller_height = [this, rows_scroller]() { if (!rows_scroller) return; @@ -7581,6 +7603,7 @@ void Sidebar::add_custom_filament(wxColour new_col) { if (p->combos_filament.size() >= MAXIMUM_EXTRUDER_NUMBER) return; int filament_count = p->combos_filament.size() + 1; + wxGetApp().plater()->confirm_auto_generated_gradients(filament_count); std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); wxGetApp().plater()->on_filaments_change(filament_count); @@ -8409,9 +8432,13 @@ struct Plater::priv static const std::regex pattern_prusa; bool m_is_dark = false; + size_t m_last_auto_gradient_prompt_physical_count = 0; + bool m_last_auto_gradient_prompt_accepted = false; priv(Plater *q, MainFrame *main_frame); ~priv(); + bool confirm_auto_generated_gradients(wxWindow *parent, size_t num_physical); + void set_auto_generated_gradient_decision(size_t num_physical, bool create_auto_gradients); bool need_update() const { return m_need_update; } @@ -9933,9 +9960,10 @@ std::vector Plater::priv::load_files(const std::vector& input_ "mixed_filament_surface_indentation" }; preset_bundle->project_config.apply_only(config_loaded, imported_project_option_keys, true); - if (current_num_filaments != desired_physical_filaments) + if (current_num_filaments != desired_physical_filaments) { + q->confirm_auto_generated_gradients(desired_physical_filaments); preset_bundle->set_num_filaments(unsigned(desired_physical_filaments)); - else + } else preset_bundle->update_multi_material_filament_presets(); BOOST_LOG_TRIVIAL(info) << "3MF geometry import applied imported project config" << " current_num_filaments=" << current_num_filaments @@ -9948,6 +9976,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ new_colors.assign(imported_filament_colors.begin() + current_num_filaments, imported_filament_colors.begin() + desired_physical_filaments); } + q->confirm_auto_generated_gradients(desired_physical_filaments); preset_bundle->set_num_filaments(unsigned(desired_physical_filaments), new_colors); wxGetApp().plater()->on_filaments_change(desired_physical_filaments); } @@ -9956,6 +9985,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ int filament_size = sidebar->combos_filament().size(); while (filament_size < MAXIMUM_EXTRUDER_NUMBER && filament_size < size) { int filament_count = filament_size + 1; + wxGetApp().plater()->confirm_auto_generated_gradients(filament_count); wxColour new_col = Plater::get_next_color_for_filament(); std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); @@ -13746,6 +13776,76 @@ void Plater::priv::on_repair_model(wxCommandEvent &event) wxGetApp().obj_list()->fix_through_netfabb(); } +bool Plater::priv::confirm_auto_generated_gradients(wxWindow *parent, size_t num_physical) +{ + auto *app_config = wxGetApp().app_config; + if (app_config == nullptr) + return MixedFilamentManager::auto_generate_enabled(); + + const bool pref_enabled = app_config->get_bool("auto_generate_gradients"); + if (!pref_enabled) { + m_last_auto_gradient_prompt_physical_count = 0; + m_last_auto_gradient_prompt_accepted = false; + MixedFilamentManager::set_auto_generate_enabled(false); + return false; + } + + if (num_physical <= 4) { + m_last_auto_gradient_prompt_physical_count = 0; + m_last_auto_gradient_prompt_accepted = false; + MixedFilamentManager::set_auto_generate_enabled(true); + return true; + } + + if (parent == nullptr || !parent->IsShownOnScreen()) { + m_last_auto_gradient_prompt_physical_count = 0; + m_last_auto_gradient_prompt_accepted = false; + MixedFilamentManager::set_auto_generate_enabled(true); + return true; + } + + if (m_last_auto_gradient_prompt_physical_count == num_physical) { + MixedFilamentManager::set_auto_generate_enabled(m_last_auto_gradient_prompt_accepted); + return m_last_auto_gradient_prompt_accepted; + } + + const size_t auto_gradient_count = num_physical * (num_physical - 1) / 2; + const wxString message = wxString::Format( + _L("Using %d physical filaments will create %d auto-generated gradients.\nDo you want to create them now?"), + int(num_physical), + int(auto_gradient_count)); + const int result = MessageDialog(parent, + message, + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Auto gradients"), + wxYES_NO | wxYES_DEFAULT | wxCENTRE | wxICON_QUESTION) + .ShowModal(); + const bool accepted = result == wxID_YES; + m_last_auto_gradient_prompt_physical_count = num_physical; + m_last_auto_gradient_prompt_accepted = accepted; + MixedFilamentManager::set_auto_generate_enabled(accepted); + return accepted; +} + +void Plater::priv::set_auto_generated_gradient_decision(size_t num_physical, bool create_auto_gradients) +{ + m_last_auto_gradient_prompt_physical_count = num_physical; + m_last_auto_gradient_prompt_accepted = create_auto_gradients; + MixedFilamentManager::set_auto_generate_enabled(create_auto_gradients); +} + +bool Plater::confirm_auto_generated_gradients(size_t num_physical) +{ + return p != nullptr ? p->confirm_auto_generated_gradients(this, num_physical) : MixedFilamentManager::auto_generate_enabled(); +} + +void Plater::set_auto_generated_gradient_decision(size_t num_physical, bool create_auto_gradients) +{ + if (p != nullptr) + p->set_auto_generated_gradient_decision(num_physical, create_auto_gradients); + else + MixedFilamentManager::set_auto_generate_enabled(create_auto_gradients); +} + void Plater::priv::on_filament_color_changed(wxCommandEvent &event) { //q->update_all_plate_thumbnails(true); @@ -13760,7 +13860,9 @@ void Plater::priv::on_filament_color_changed(wxCommandEvent &event) sidebar->auto_calc_flushing_volumes(modify_id); } - // Regenerate mixed filaments and update the sidebar panel. + // Regenerate mixed filaments and refresh the mixed panel only. Color + // changes do not alter filament IDs, so the full on_filaments_change() + // path is unnecessary and can re-enter UI rebuilds mid-update. wxGetApp().preset_bundle->update_multi_material_filament_presets(); sidebar->update_mixed_filament_panel(); } @@ -19647,6 +19749,8 @@ void Plater::on_filaments_change(size_t num_filaments) update_filament_colors_in_full_config(); const size_t old_num_filaments = sidebar().combos_filament().size(); + const bool auto_generate_before = MixedFilamentManager::auto_generate_enabled(); + const bool allow_auto_gradients = p->confirm_auto_generated_gradients(this, num_filaments); auto summarize_uint_vector = [](const std::vector &values, size_t max_items = 24) { std::string out = "["; const size_t n = std::min(values.size(), max_items); @@ -19681,6 +19785,8 @@ void Plater::on_filaments_change(size_t num_filaments) return out; }; PresetBundle *preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle != nullptr && auto_generate_before && !allow_auto_gradients) + preset_bundle->update_multi_material_filament_presets(size_t(-1), old_num_filaments); // Consume remap before sidebar refresh, which may trigger config sync // paths that regenerate mixed filaments and clear this remap buffer. std::vector id_remap; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index c692b6afb4..d96bb5b262 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -492,6 +492,8 @@ public: void on_filaments_change(size_t extruders_count); void on_filaments_delete(size_t extruders_count, size_t filament_id, int replace_filament_id = -1); + bool confirm_auto_generated_gradients(size_t num_physical); + void set_auto_generated_gradient_decision(size_t num_physical, bool create_auto_gradients); // BBS void on_bed_type_change(BedType bed_type); bool update_filament_colors_in_full_config(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 45725d4b70..c633e101b6 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -6,6 +6,7 @@ #include "MsgDialog.hpp" #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" +#include "libslic3r/MixedFilament.hpp" #include #include #include "Notebook.hpp" @@ -778,6 +779,16 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false"); } + if (param == "auto_generate_gradients") { + MixedFilamentManager::set_auto_generate_enabled(checkbox->GetValue()); + if (wxGetApp().preset_bundle != nullptr && wxGetApp().plater() != nullptr) { + const size_t num_physical = wxGetApp().preset_bundle->filament_presets.size(); + wxGetApp().plater()->set_auto_generated_gradient_decision(num_physical, checkbox->GetValue()); + wxGetApp().preset_bundle->update_multi_material_filament_presets(); + wxGetApp().plater()->on_filaments_change(num_physical); + } + } + #ifdef __WXMSW__ if (param == "associate_3mf") { bool pbool = app_config->get("associate_3mf") == "true" ? true : false; @@ -1041,6 +1052,8 @@ PreferencesDialog::PreferencesDialog(wxWindow *parent, wxWindowID id, const wxSt j["auto_flushing"] = value; value = wxGetApp().app_config->get("auto_calculate_when_filament_change"); j["auto_calculate_when_filament_change"] = value; + value = wxGetApp().app_config->get("auto_generate_gradients"); + j["auto_generate_gradients"] = value; agent->track_event("preferences_changed", j.dump()); } } catch(...) {} @@ -1228,6 +1241,8 @@ wxWindow* PreferencesDialog::create_general_page() auto item_calc_mode = create_item_checkbox(_L("Flushing volumes: Auto-calculate every time the color changed."), page, _L("If enabled, auto-calculate every time the color changed."), 50, "auto_calculate"); auto item_calc_in_long_retract = create_item_checkbox(_L("Flushing volumes: Auto-calculate every time when the filament is changed."), page, _L("If enabled, auto-calculate every time when filament is changed"), 50, "auto_calculate_when_filament_change"); + auto item_auto_generate_gradients = create_item_checkbox(_L("Mixed filaments: Auto-generate gradients."), page, _L("If enabled, Snapmaker Orca automatically creates gradient mixed filaments from physical filament pairs."), 50, "auto_generate_gradients"); + auto title_full_spectrum = create_item_title(_devL("FullSpectrum"), page, _devL("FullSpectrum")); auto item_remember_printer_config = create_item_checkbox(_L("Remember printer configuration"), page, _L("If enabled, Orca will remember and switch filament/process configuration for each printer automatically."), 50, "remember_printer_config"); auto item_step_mesh_setting = create_item_checkbox(_L("Show the step mesh parameter setting dialog."), page, _L("If enabled,a parameter settings dialog will appear during STEP file import."), 50, "enable_step_mesh_setting"); auto item_multi_machine = create_item_checkbox(_L("Multi-device Management (Take effect after restarting Snapmaker Orca)."), page, _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), 50, "enable_multi_machine"); @@ -1328,16 +1343,18 @@ wxWindow* PreferencesDialog::create_general_page() sizer_page->Add(camera_orbit_mult, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_show_splash_screen, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_hints, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_calc_in_long_retract, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_multi_machine, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_step_mesh_setting, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_auto_arrange, 0, wxTOP, FromDIP(3)); sizer_page->Add(title_presets, 0, wxTOP | wxEXPAND, FromDIP(20)); sizer_page->Add(item_calc_mode, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_calc_in_long_retract, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_user_sync, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_system_sync, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_remember_printer_config, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_save_presets, 0, wxTOP, FromDIP(3)); + sizer_page->Add(title_full_spectrum, 0, wxTOP | wxEXPAND, FromDIP(20)); + sizer_page->Add(item_auto_generate_gradients, 0, wxTOP, FromDIP(3)); //sizer_page->Add(title_network, 0, wxTOP | wxEXPAND, FromDIP(20)); //sizer_page->Add(item_check_stable_version_only, 0, wxTOP, FromDIP(3)); diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index 8fbeb9fbb4..1849a5748d 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -46,6 +46,22 @@ static unsigned int virtual_id_for_stable_id(const std::vector &m return 0; } +struct MixedAutoGenerateGuard +{ + explicit MixedAutoGenerateGuard(bool enabled) + : previous(MixedFilamentManager::auto_generate_enabled()) + { + MixedFilamentManager::set_auto_generate_enabled(enabled); + } + + ~MixedAutoGenerateGuard() + { + MixedFilamentManager::set_auto_generate_enabled(previous); + } + + bool previous = true; +}; + } // namespace TEST_CASE("Mixed filament remap follows stable row ids when same-pair rows reorder", "[MixedFilament]") @@ -146,6 +162,32 @@ TEST_CASE("Mixed filament grouped manual patterns normalize and round-trip", "[M CHECK(loaded.mixed_filaments().front().mix_b_percent == 13); } +TEST_CASE("Mixed filament auto generation can be disabled without dropping custom rows", "[MixedFilament]") +{ + const std::vector colors = {"#FF0000", "#00FF00", "#0000FF"}; + + MixedFilamentManager enabled_mgr; + enabled_mgr.auto_generate(colors); + REQUIRE(enabled_mgr.mixed_filaments().size() == 3); + const std::string serialized_auto_rows = enabled_mgr.serialize_custom_entries(); + + MixedAutoGenerateGuard guard(false); + + MixedFilamentManager mgr; + mgr.add_custom_filament(1, 2, 50, colors); + REQUIRE(mgr.mixed_filaments().size() == 1); + + mgr.auto_generate(colors); + REQUIRE(mgr.mixed_filaments().size() == 1); + CHECK(mgr.mixed_filaments().front().custom); + CHECK(mgr.mixed_filaments().front().component_a == 1); + CHECK(mgr.mixed_filaments().front().component_b == 2); + + MixedFilamentManager loaded; + loaded.load_custom_entries(serialized_auto_rows, colors); + CHECK(loaded.mixed_filaments().empty()); +} + TEST_CASE("Mixed filament perimeter resolver uses grouped manual patterns by inset", "[MixedFilament]") { const std::vector colors = {"#00FFFF", "#FF00FF"};