From ade01c68d3a6192912025b55ca527ed7ee2b0bdf Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Tue, 19 May 2026 20:24:18 +0100 Subject: [PATCH] Bugfixes - Allow pasting hex codes in color picker on macos - Prevent texture mapping zone panel resetting to top - Fix solid rectangle showing in projection panel while text is being rendered - Allow textures to be restored from crash backups --- src/libslic3r/Format/bbs_3mf.cpp | 50 ++++++++++++- src/libslic3r/TextureMapping.hpp | 2 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 26 +++---- src/slic3r/GUI/MainFrame.cpp | 7 +- src/slic3r/GUI/Plater.cpp | 40 ++++++++++- src/slic3r/Utils/MacDarkMode.hpp | 3 + src/slic3r/Utils/MacDarkMode.mm | 71 +++++++++++++++++++ 7 files changed, 179 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 8b8b88c35dd..6c0bb95f341 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -1950,6 +1950,7 @@ static void append_triangle_material_data(std::vector &uv_valid, void _generate_current_object_list(std::vector &sub_objects, Id object_id, IdToCurrentObjectMap& current_objects); bool _generate_volumes_new(ModelObject& object, const std::vector &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); + bool _try_extract_imported_obj_texture_from_archive(mz_zip_archive &archive, const std::string &image_file, std::vector &image_payload); void _restore_imported_obj_textures_from_archive(mz_zip_archive &archive); void _restore_prime_tower_texture_from_archive(mz_zip_archive &archive, const DynamicPrintConfig &config, Model &model); //bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); @@ -5931,6 +5932,53 @@ static void append_triangle_material_data(std::vector &uv_valid, return true; } + bool _BBS_3MF_Importer::_try_extract_imported_obj_texture_from_archive(mz_zip_archive &archive, const std::string &image_file, std::vector &image_payload) + { + image_payload.clear(); + if (try_extract_file_from_archive(archive, image_file, image_payload)) + return true; + + if (!m_load_restore) + return false; + + for (const std::string &sub_model_path : m_sub_model_paths) { + std::string path = sub_model_path; + std::replace(path.begin(), path.end(), '\\', '/'); + while (!path.empty() && path.front() == '/') + path.erase(path.begin()); + if (path.empty()) + continue; + + const boost::filesystem::path object_archive_path = boost::filesystem::path(m_backup_path) / path; + if (!boost::filesystem::exists(object_archive_path)) + continue; + + mz_zip_archive object_archive; + mz_zip_zero_struct(&object_archive); + if (!open_zip_reader(&object_archive, object_archive_path.string())) + continue; + + const bool found = try_extract_file_from_archive(object_archive, image_file, image_payload); + close_zip_reader(&object_archive); + if (found) + return true; + } + + if (!m_origin_file.empty() && boost::filesystem::exists(m_origin_file)) { + mz_zip_archive origin_archive; + mz_zip_zero_struct(&origin_archive); + if (open_zip_reader(&origin_archive, m_origin_file)) { + const bool found = try_extract_file_from_archive(origin_archive, image_file, image_payload); + close_zip_reader(&origin_archive); + if (found) + return true; + } + } + + image_payload.clear(); + return false; + } + void _BBS_3MF_Importer::_restore_imported_obj_textures_from_archive(mz_zip_archive &archive) { if (m_model == nullptr) @@ -5946,7 +5994,7 @@ static void append_triangle_material_data(std::vector &uv_valid, continue; std::vector image_payload; - if (!try_extract_file_from_archive(archive, source.image_file, image_payload)) { + if (!_try_extract_imported_obj_texture_from_archive(archive, source.image_file, image_payload)) { BOOST_LOG_TRIVIAL(warning) << "3MF texture2d payload missing for image='" << source.image_file << "'"; continue; } diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index b08082ed3b3..d4ad018551d 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -114,7 +114,7 @@ struct TextureMappingZone static constexpr bool DefaultSeamHiding = false; static constexpr bool DefaultNonlinearOffsetAdjustment = false; static constexpr int DefaultModulationMode = int(ModulationLineWidth); - static constexpr bool DefaultRecolorSmallPerimeterLoops = true; + static constexpr bool DefaultRecolorSmallPerimeterLoops = false; static constexpr bool DefaultRecolorTopVisiblePerimeterSections = false; static constexpr int DefaultTopVisiblePerimeterRecolorAggressiveness = int(TopVisibleRecolorAggressive); static constexpr bool DefaultCompactOffsetMode = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 5666524e622..df6db774bca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -13498,19 +13498,6 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott ensure_text_projection_image(); update_default_projection_mode(); - if (m_show_overlay && ensure_overlay_texture()) { - const OverlayRect rect = overlay_rect(); - if (rect.width > 0.f && rect.height > 0.f) { - ImGui::GetBackgroundDrawList()->AddImage((void *)(intptr_t)m_overlay_texture.get_id(), - ImVec2(rect.left, rect.top), - ImVec2(rect.left + rect.width, rect.top + rect.height), - ImVec2(0.f, 0.f), - ImVec2(1.f, 1.f), - ImGui::GetColorU32( - ImVec4(1.f, 1.f, 1.f, 0.72f * std::clamp(m_projection_opacity, 0.f, 1.f)))); - } - } - const float approx_height = m_imgui->scaled(12.0f); y = std::min(y, bottom_limit - approx_height); GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always); @@ -13649,6 +13636,19 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott GizmoImguiEnd(); ImGuiWrapper::pop_toolbar_style(); + + if (m_show_overlay && ensure_overlay_texture()) { + const OverlayRect rect = overlay_rect(); + if (rect.width > 0.f && rect.height > 0.f) { + ImGui::GetBackgroundDrawList()->AddImage((void *)(intptr_t)m_overlay_texture.get_id(), + ImVec2(rect.left, rect.top), + ImVec2(rect.left + rect.width, rect.top + rect.height), + ImVec2(0.f, 0.f), + ImVec2(1.f, 1.f), + ImGui::GetColorU32( + ImVec4(1.f, 1.f, 1.f, 0.72f * std::clamp(m_projection_opacity, 0.f, 1.f)))); + } + } } bool GLGizmoImageProjection::project_image_to_selected_object() diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index cbcbd0adaf9..c7c756875d2 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -300,6 +300,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ { #ifdef __WXOSX__ set_miniaturizable(GetHandle()); + mac_install_text_paste_shortcut(); #endif #ifdef __WXGTK__ @@ -2899,6 +2900,9 @@ void MainFrame::init_menubar_as_editor() // BBS Paste append_menu_item(editMenu, wxID_ANY, _L("Paste") + sep + ctrl_t + "V", _L("Paste clipboard"), [this, handle_key_event](wxCommandEvent&) { + if (mac_paste_to_text_control()) { + return; + } wxKeyEvent e; e.SetEventType(wxEVT_KEY_DOWN); e.SetControlDown(true); @@ -2907,7 +2911,7 @@ void MainFrame::init_menubar_as_editor() return; } m_plater->paste_from_clipboard(); }, - "", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); + "", nullptr, [this](){return mac_can_paste_to_text_control() || m_plater->can_paste_from_clipboard(); }, this); #if 0 // BBS Delete selected append_menu_item(editMenu, wxID_ANY, _L("Delete selected") + "\t" + _L("Backspace"), @@ -4346,4 +4350,3 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) } // GUI } // Slic3r - diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3aa6b21e02c..50f6388a597 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -719,6 +719,21 @@ static wxSize from_dip_for_parent(wxWindow *parent, const wxSize &size) return wxWindow::FromDIP(size, parent); } +class TextureMappingScrolledWindow : public wxScrolledWindow +{ +public: + TextureMappingScrolledWindow(wxWindow *parent, + wxWindowID id = wxID_ANY, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + long style = wxVSCROLL) + : wxScrolledWindow(parent, id, pos, size, style) + { + } + + bool ShouldScrollToChildOnFocus(wxWindow *child) override { return false; } +}; + class TextureMappingPatternPreview : public wxPanel { public: @@ -1353,7 +1368,7 @@ public: m_recolor_top_visible_perimeter_sections_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { update_modulation_mode_options_visibility(false); }); auto *top_visible_recolor_row = new wxBoxSizer(wxHORIZONTAL); m_top_visible_perimeter_recolor_aggressiveness_label = - new wxStaticText(print_settings_page, wxID_ANY, _L("Top-visible recolor sensitivity:")); + new wxStaticText(print_settings_page, wxID_ANY, _L("Top layer line recolor sensitivity:")); top_visible_recolor_row->Add(m_top_visible_perimeter_recolor_aggressiveness_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); wxArrayString top_visible_recolor_choices; top_visible_recolor_choices.Add(_L("Conservative")); @@ -4090,7 +4105,7 @@ Sidebar::Sidebar(Plater *parent) spliter_texture_2->SetLineColour("#CECECE"); scrolled_sizer->Add(spliter_texture_2, 0, wxEXPAND); - p->m_panel_texture_mapping_content = new wxScrolledWindow(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + p->m_panel_texture_mapping_content = new TextureMappingScrolledWindow(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); p->m_panel_texture_mapping_content->SetScrollRate(0, 5); p->m_panel_texture_mapping_content->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT); p->m_panel_texture_mapping_content->SetBackgroundColour(wxGetApp().dark_mode() ? wxColour(45, 45, 49) : wxColour(255, 255, 255)); @@ -5659,6 +5674,19 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) wxWindowUpdateLocker no_updates_sidebar(this); wxWindowUpdateLocker no_updates_panel(p->m_panel_texture_mapping_content); + int texture_mapping_scroll_x = 0; + int texture_mapping_scroll_y = 0; + p->m_panel_texture_mapping_content->GetViewStart(&texture_mapping_scroll_x, &texture_mapping_scroll_y); + + auto scroll_texture_mapping_content_to = [this](int scroll_x, int scroll_y) { + if (p->m_panel_texture_mapping_content != nullptr) + p->m_panel_texture_mapping_content->Scroll(scroll_x, scroll_y); + }; + + auto restore_texture_mapping_scroll = [scroll_texture_mapping_content_to, texture_mapping_scroll_x, texture_mapping_scroll_y]() { + scroll_texture_mapping_content_to(texture_mapping_scroll_x, texture_mapping_scroll_y); + }; + PresetBundle *bundle = wxGetApp().preset_bundle; if (bundle == nullptr) return; @@ -5735,9 +5763,12 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) return; content_sizer->Clear(true); content_sizer->AddSpacer(FromDIP(SidebarProps::ContentMargin())); - auto update_texture_mapping_area_height = [this]() { + auto update_texture_mapping_area_height = [this, scroll_texture_mapping_content_to]() { if (p->m_panel_texture_mapping_content == nullptr || p->m_panel_texture_mapping_content->GetSizer() == nullptr) return; + int scroll_x = 0; + int scroll_y = 0; + p->m_panel_texture_mapping_content->GetViewStart(&scroll_x, &scroll_y); const int max_height = FromDIP(260); p->m_panel_texture_mapping_content->SetMaxSize(wxSize(-1, max_height)); p->m_panel_texture_mapping_content->Layout(); @@ -5746,6 +5777,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) if (min_size.y > max_height) min_size.y = max_height; p->m_panel_texture_mapping_content->SetMinSize(wxSize(-1, std::max(FromDIP(1), min_size.y))); + scroll_texture_mapping_content_to(scroll_x, scroll_y); }; if (p->m_btn_add_texture_map != nullptr) @@ -5839,6 +5871,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) update_texture_mapping_area_height(); m_scrolled_sizer->Layout(); Layout(); + restore_texture_mapping_scroll(); return; } @@ -6395,6 +6428,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) update_texture_mapping_area_height(); m_scrolled_sizer->Layout(); Layout(); + restore_texture_mapping_scroll(); } std::vector Sidebar::get_ui_ordered_filament_ids() const diff --git a/src/slic3r/Utils/MacDarkMode.hpp b/src/slic3r/Utils/MacDarkMode.hpp index 435a164d1e1..b8a6321ce12 100644 --- a/src/slic3r/Utils/MacDarkMode.hpp +++ b/src/slic3r/Utils/MacDarkMode.hpp @@ -9,6 +9,9 @@ namespace GUI { #if __APPLE__ extern bool mac_dark_mode(); extern double mac_max_scaling_factor(); +extern bool mac_can_paste_to_text_control(); +extern bool mac_paste_to_text_control(); +extern void mac_install_text_paste_shortcut(); extern void set_miniaturizable(void * window); void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)); void WKWebView_setTransparentBackground(void * web); diff --git a/src/slic3r/Utils/MacDarkMode.mm b/src/slic3r/Utils/MacDarkMode.mm index cecd90044b9..101b11d35a0 100644 --- a/src/slic3r/Utils/MacDarkMode.mm +++ b/src/slic3r/Utils/MacDarkMode.mm @@ -41,6 +41,77 @@ double mac_max_scaling_factor() } return scaling; } + +static id mac_text_paste_target() +{ + id target = [NSApp targetForAction:@selector(paste:) to:nil from:nil]; + if (target != nil && [target isKindOfClass:[NSText class]]) + return target; + return nil; +} + +static NSString *mac_color_panel_hex_string() +{ + NSWindow *window = [NSApp keyWindow]; + if (![window isKindOfClass:[NSColorPanel class]]) + return nil; + + NSString *string = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]; + string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (![string hasPrefix:@"#"]) + return nil; + + NSString *value = [string substringFromIndex:1]; + if ([value length] != 6) + return nil; + + NSCharacterSet *non_hex = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"] invertedSet]; + if ([value rangeOfCharacterFromSet:non_hex].location != NSNotFound) + return nil; + + return value; +} + +bool mac_can_paste_to_text_control() +{ + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + if ([pasteboard stringForType:NSPasteboardTypeString] == nil) + return false; + return mac_text_paste_target() != nil; +} + +bool mac_paste_to_text_control() +{ + id target = mac_text_paste_target(); + if (target == nil) + return false; + + NSString *color_panel_hex = mac_color_panel_hex_string(); + if (color_panel_hex != nil && [target respondsToSelector:@selector(insertText:)]) { + [target insertText:color_panel_hex]; + return true; + } + + return [NSApp sendAction:@selector(paste:) to:nil from:nil] == YES; +} + +void mac_install_text_paste_shortcut() +{ + static id monitor = nil; + if (monitor != nil) + return; + + monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent *(NSEvent *event) { + NSEventModifierFlags flags = [event modifierFlags]; + if ((flags & NSEventModifierFlagCommand) != 0 && + (flags & (NSEventModifierFlagShift | NSEventModifierFlagControl | NSEventModifierFlagOption)) == 0) { + NSString *characters = [event charactersIgnoringModifiers]; + if ([characters length] == 1 && [[characters lowercaseString] isEqualToString:@"v"] && mac_paste_to_text_control()) + return nil; + } + return event; + }]; +} void set_miniaturizable(void * window) {