Copy/paste works more intuitively in macos color dialog. Make sure crash backup updates when color data is changed

This commit is contained in:
sentientstardust
2026-05-19 21:49:46 +01:00
parent ade01c68d3
commit 11a6b694d8
5 changed files with 198 additions and 16 deletions

View File

@@ -14,6 +14,7 @@
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Tab.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TextureMapping.hpp"
#include "libslic3r/ColorSolver.hpp"
@@ -70,6 +71,47 @@ constexpr float SlopeAutoPaintMaxAngleDeg = 180.f;
constexpr float ProjectionTextRasterBaseFontSize = 200.f;
constexpr int ProjectionTextRasterMaxDimension = 2048;
constexpr int ProjectionTextRasterMinFontSize = 18;
static bool queue_color_data_crash_backup(ModelObject *object)
{
if (object == nullptr ||
object->get_model() == nullptr ||
!object->get_model()->is_need_backup() ||
object->volumes.empty() ||
object->instances.empty())
return false;
Slic3r::save_object_mesh(*object);
return true;
}
static ModelObject *find_model_object_by_id(const ObjectID &object_id)
{
for (ModelObject *object : wxGetApp().model().objects)
if (object != nullptr && object->id() == object_id)
return object;
return nullptr;
}
static void remember_color_data_crash_backup_object(std::vector<ObjectID> &object_ids, const ModelObject *object)
{
if (object == nullptr)
return;
const ObjectID object_id = object->id();
if (std::find(object_ids.begin(), object_ids.end(), object_id) == object_ids.end())
object_ids.emplace_back(object_id);
}
static void queue_color_data_crash_backups(std::vector<ObjectID> &object_ids)
{
bool queued = false;
for (const ObjectID &object_id : object_ids)
queued |= queue_color_data_crash_backup(find_model_object_by_id(object_id));
object_ids.clear();
if (queued)
Slic3r::backup_soon();
}
}
struct ManagedRegionColorSource
@@ -8226,6 +8268,8 @@ public:
m_background_clear->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { clear_background_color(); });
}
bool changed() const { return m_changed; }
private:
struct Row
{
@@ -8578,6 +8622,7 @@ private:
void refresh_object_after_change()
{
m_changed = true;
refresh_managed_color_data_object(m_canvas, m_object);
notify_object_changed();
m_canvas.set_as_dirty();
@@ -8620,6 +8665,7 @@ private:
std::function<void()> m_on_object_changed;
wxPanel *m_background_picker = nullptr;
wxButton *m_background_clear = nullptr;
bool m_changed = false;
std::vector<Row> m_rows;
std::vector<wxWindow *> m_raw_image_texture_info_windows;
wxStaticText *m_raw_image_texture_status = nullptr;
@@ -8634,6 +8680,8 @@ void open_color_data_management_dialog(wxWindow *parent, GLCanvas3D &canvas, Mod
ColorDataManagementDialog dialog(parent, canvas, object, std::move(on_object_changed));
dialog.ShowModal();
if (dialog.changed() && queue_color_data_crash_backup(object))
Slic3r::backup_soon();
}
void GLGizmoMmuSegmentation::init_extruders_data(const std::vector<ColorRGBA> &extruder_colors)
@@ -10807,6 +10855,8 @@ void GLGizmoMmuSegmentation::convert_selected_object_vertex_colors_to_texture_ma
wxGetApp().plater()->get_partplate_list().notify_instance_update(object_idx, 0);
}
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
if (queue_color_data_crash_backup(object))
Slic3r::backup_soon();
}
void GLGizmoMmuSegmentation::convert_selected_object_image_texture_to_texture_mapping_colors()
@@ -10878,6 +10928,8 @@ void GLGizmoMmuSegmentation::convert_selected_object_image_texture_to_texture_ma
wxGetApp().plater()->get_partplate_list().notify_instance_update(object_idx, 0);
}
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
if (queue_color_data_crash_backup(object))
Slic3r::backup_soon();
}
void GLGizmoMmuSegmentation::convert_selected_regions_to_vertex_colors()
@@ -10958,6 +11010,8 @@ void GLGizmoMmuSegmentation::finish_selected_regions_color_data_conversion(Model
wxGetApp().plater()->get_partplate_list().notify_instance_update(object_idx, 0);
}
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
if (queue_color_data_crash_backup(&object))
Slic3r::backup_soon();
m_parent.render();
}
@@ -11119,6 +11173,14 @@ void GLGizmoTrueColorPainting::on_shutdown()
m_parent.toggle_model_objects_visibility(true);
}
void GLGizmoTrueColorPainting::on_set_state()
{
GLGizmoPainterBase::on_set_state();
if (get_state() == Off)
backup_changed_rgb_data_objects();
}
PainterGizmoType GLGizmoTrueColorPainting::get_painter_type() const
{
return PainterGizmoType::TRUE_COLOR;
@@ -11845,6 +11907,7 @@ void GLGizmoTrueColorPainting::convert_selected_object_image_texture_to_rgb_data
void GLGizmoTrueColorPainting::refresh_selected_object_after_rgb_change(ModelObject *object)
{
remember_changed_rgb_data_object(object);
update_selected_object_color_state();
init_model_triangle_selectors();
m_parent.update_volumes_colors_by_extruder();
@@ -11859,6 +11922,16 @@ void GLGizmoTrueColorPainting::refresh_selected_object_after_rgb_change(ModelObj
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLGizmoTrueColorPainting::remember_changed_rgb_data_object(ModelObject *object)
{
remember_color_data_crash_backup_object(m_changed_rgb_data_object_ids, object);
}
void GLGizmoTrueColorPainting::backup_changed_rgb_data_objects()
{
queue_color_data_crash_backups(m_changed_rgb_data_object_ids);
}
bool GLGizmoTrueColorPainting::pick_color_from_model(const Vec2d &mouse_position)
{
ColorRGBA color;
@@ -12639,6 +12712,7 @@ void GLGizmoTrueColorPainting::refresh_selected_object_after_background_color_ch
if (object == nullptr)
return;
remember_changed_rgb_data_object(object);
cancel_rgb_data_preview_conversion();
m_preview_rgb_data_volume_ids.clear();
m_preview_rgb_data_by_volume.clear();
@@ -12952,6 +13026,7 @@ void GLGizmoImageProjection::on_set_state()
} else if (get_state() == Off) {
m_parent.enable_picking(true);
m_parent.toggle_model_objects_visibility(true);
backup_projected_objects();
}
}
@@ -13762,6 +13837,7 @@ bool GLGizmoImageProjection::project_image_to_selected_object()
}
refresh_projected_object(object);
remember_projected_object(object);
m_projection_mode_initialized = true;
m_projection_mode_object_id = object->id();
if (converting_raw_to_rgba_image && !selected_object_has_raw_atlas_texture_data())
@@ -14442,6 +14518,16 @@ void GLGizmoImageProjection::refresh_projected_object(ModelObject *object)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLGizmoImageProjection::remember_projected_object(ModelObject *object)
{
remember_color_data_crash_backup_object(m_projected_object_ids, object);
}
void GLGizmoImageProjection::backup_projected_objects()
{
queue_color_data_crash_backups(m_projected_object_ids);
}
void GLMmSegmentationGizmo3DScene::release_geometry() {
if (this->vertices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));

View File

@@ -309,6 +309,7 @@ private:
void update_from_model_object(bool first_update = false) override;
void on_opening() override;
void on_shutdown() override;
void on_set_state() override;
PainterGizmoType get_painter_type() const override;
void init_model_triangle_selectors();
@@ -322,6 +323,8 @@ private:
void convert_selected_object_vertex_colors_to_rgb_data();
void convert_selected_object_image_texture_to_rgb_data();
void refresh_selected_object_after_rgb_change(ModelObject *object);
void remember_changed_rgb_data_object(ModelObject *object);
void backup_changed_rgb_data_objects();
void update_triangle_selectors_color();
void update_rgb_data_preview_conversion();
void start_rgb_data_preview_conversion(ModelObject &object);
@@ -391,6 +394,7 @@ private:
bool m_color_picker_active = false;
bool m_brush_stroke_active = false;
ObjectID m_selected_color_state_object_id;
std::vector<ObjectID> m_changed_rgb_data_object_ids;
std::vector<std::vector<Vec3f>> m_brush_stroke_points_by_volume;
std::vector<ObjectID> m_preview_rgb_data_volume_ids;
std::vector<std::unique_ptr<ColorFacetsAnnotation>> m_preview_rgb_data_by_volume;
@@ -477,6 +481,8 @@ private:
bool project_to_image_texture(ModelObject *object);
bool project_to_rgb_data(ModelObject *object);
void refresh_projected_object(ModelObject *object);
void remember_projected_object(ModelObject *object);
void backup_projected_objects();
ProjectionMode m_projection_mode = ProjectionMode::RGBData;
bool m_projection_mode_initialized = false;
@@ -512,6 +518,7 @@ private:
bool m_apply_transparency_as_background = false;
bool m_pass_through_model = false;
bool m_convert_existing_colors_to_raw_offsets = true;
std::vector<ObjectID> m_projected_object_ids;
};
} // namespace Slic3r

View File

@@ -2888,6 +2888,9 @@ void MainFrame::init_menubar_as_editor()
// BBS Copy
append_menu_item(editMenu, wxID_ANY, _L("Copy") + sep + ctrl_t + "C",
_L("Copy selection to clipboard"), [this, handle_key_event](wxCommandEvent&) {
if (mac_copy_to_text_control()) {
return;
}
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
e.SetControlDown(true);
@@ -2896,7 +2899,7 @@ void MainFrame::init_menubar_as_editor()
return;
}
m_plater->copy_selection_to_clipboard(); },
"", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
"", nullptr, [this](){return mac_can_copy_to_text_control() || m_plater->can_copy_to_clipboard(); }, this);
// BBS Paste
append_menu_item(editMenu, wxID_ANY, _L("Paste") + sep + ctrl_t + "V",
_L("Paste clipboard"), [this, handle_key_event](wxCommandEvent&) {

View File

@@ -9,7 +9,9 @@ namespace GUI {
#if __APPLE__
extern bool mac_dark_mode();
extern double mac_max_scaling_factor();
extern bool mac_can_copy_to_text_control();
extern bool mac_can_paste_to_text_control();
extern bool mac_copy_to_text_control();
extern bool mac_paste_to_text_control();
extern void mac_install_text_paste_shortcut();
extern void set_miniaturizable(void * window);

View File

@@ -42,36 +42,95 @@ double mac_max_scaling_factor()
return scaling;
}
static id mac_text_paste_target()
static id mac_text_target_for_action(SEL action)
{
id target = [NSApp targetForAction:@selector(paste:) to:nil from:nil];
id target = [NSApp targetForAction:action to:nil from:nil];
if (target != nil && [target isKindOfClass:[NSText class]])
return target;
return nil;
id responder = [[NSApp keyWindow] firstResponder];
return [responder isKindOfClass:[NSText class]] ? responder : nil;
}
static id mac_text_paste_target()
{
return mac_text_target_for_action(@selector(paste:));
}
static id mac_text_copy_target()
{
return mac_text_target_for_action(@selector(copy:));
}
static bool mac_text_target_has_selection(id target)
{
return [target respondsToSelector:@selector(selectedRange)] && [target selectedRange].length > 0;
}
static bool mac_string_is_six_hex(NSString *string)
{
if ([string length] != 6)
return false;
NSCharacterSet *non_hex = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"] invertedSet];
return [string rangeOfCharacterFromSet:non_hex].location == NSNotFound;
}
static bool mac_string_is_hex_field_value(NSString *string)
{
NSUInteger length = [string length];
if (length < 6)
return false;
NSCharacterSet *non_hex = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"] invertedSet];
return [string rangeOfCharacterFromSet:non_hex].location == NSNotFound;
}
static bool mac_key_window_is_color_panel()
{
return [[NSApp keyWindow] isKindOfClass:[NSColorPanel class]];
}
static NSString *mac_color_panel_hex_string()
{
NSWindow *window = [NSApp keyWindow];
if (![window isKindOfClass:[NSColorPanel class]])
if (!mac_key_window_is_color_panel())
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)
NSString *value = [string hasPrefix:@"#"] ? [string substringFromIndex:1] : string;
if (!mac_string_is_six_hex(value))
return nil;
return value;
}
static NSString *mac_color_panel_hex_target_value(id target)
{
if (!mac_key_window_is_color_panel() || ![target respondsToSelector:@selector(string)])
return nil;
NSString *string = [target string];
return mac_string_is_hex_field_value(string) ? string : nil;
}
static void mac_select_hex_text_target(id target)
{
if (![target respondsToSelector:@selector(string)] || ![target respondsToSelector:@selector(setSelectedRange:)])
return;
NSString *string = mac_color_panel_hex_target_value(target);
if (string != nil)
[target setSelectedRange:NSMakeRange(0, [string length])];
}
bool mac_can_copy_to_text_control()
{
id target = mac_text_copy_target();
return target != nil && (mac_text_target_has_selection(target) || mac_color_panel_hex_target_value(target) != nil);
}
bool mac_can_paste_to_text_control()
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
@@ -80,6 +139,25 @@ bool mac_can_paste_to_text_control()
return mac_text_paste_target() != nil;
}
bool mac_copy_to_text_control()
{
id target = mac_text_copy_target();
if (target == nil)
return false;
if (!mac_text_target_has_selection(target)) {
NSString *color_panel_hex = mac_color_panel_hex_target_value(target);
if (color_panel_hex == nil)
return false;
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
return [pasteboard setString:color_panel_hex forType:NSPasteboardTypeString] == YES;
}
return [NSApp sendAction:@selector(copy:) to:nil from:nil] == YES;
}
bool mac_paste_to_text_control()
{
id target = mac_text_paste_target();
@@ -88,6 +166,7 @@ bool mac_paste_to_text_control()
NSString *color_panel_hex = mac_color_panel_hex_string();
if (color_panel_hex != nil && [target respondsToSelector:@selector(insertText:)]) {
mac_select_hex_text_target(target);
[target insertText:color_panel_hex];
return true;
}
@@ -106,8 +185,13 @@ void mac_install_text_paste_shortcut()
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;
if ([characters length] == 1) {
NSString *key = [characters lowercaseString];
if ([key isEqualToString:@"c"] && mac_copy_to_text_control())
return nil;
if ([key isEqualToString:@"v"] && mac_paste_to_text_control())
return nil;
}
}
return event;
}];