#pragma once #include "GUI_Utils.hpp" #include "Widgets/ProgressDialog.hpp" #include "libslic3r/TexturePainting.hpp" #include #include #include "Widgets/PopupWindow.hpp" #include #include #include #include "Widgets/SpinInput.hpp" #include #include #include "Widgets/Button.hpp" #include #include #include #include #include #include #include #include #include #include #include class AccentSlider; namespace Slic3r { namespace GUI { wxDECLARE_EVENT(EVT_TEXTURE_COMPUTE_DONE, wxCommandEvent); wxDECLARE_EVENT(EVT_TEXTURE_COMPUTE_PROGRESS, wxCommandEvent); wxDECLARE_EVENT(EVT_TEXTURE_COMPUTE_ERROR, wxCommandEvent); wxDECLARE_EVENT(EVT_TEXTURE_MESH_REPAIR_DECISION, wxCommandEvent); enum class TextureImportState { Idle, Computing, Ready, Error }; enum class TextureAutoMixMode { CMYW, RYBW }; enum class TextureFilamentKind { ExistingPhysical, ExistingMixed, NewPhysical, NewMixed }; struct TextureFilamentEntry { TextureFilamentKind kind{TextureFilamentKind::ExistingPhysical}; int dialog_index{-1}; size_t project_config_index{size_t(-1)}; std::string color_hex; std::string name; std::string type; std::string preset_name; std::vector mixed_components; std::vector mixed_ratios; }; struct TextureNewMixedFilament { int dialog_index{-1}; std::string color_hex; std::vector component_dialog_indices; std::vector ratios; }; struct FilamentMappingRow { int cluster_id = -1; std::array source_color = {0, 0, 0}; std::string source_hex; int target_filament_idx = 0; wxPanel* source_panel = nullptr; wxPanel* target_panel = nullptr; }; class FilamentSelectPopup; class AutoMixSelectPopup; // Lightweight 3D preview panel using wxGLCanvas. // Renders: original textured, multi-color, or filament-mapped. class TexturePreviewCanvas : public wxGLCanvas { public: enum class RenderMode { Original, MultiColor, FilamentMap }; TexturePreviewCanvas(wxWindow* parent, const wxGLAttributes& attrs); ~TexturePreviewCanvas(); void set_mesh_data( const std::vector>& vertices, const std::vector>& indices); void set_texture_data( const std::vector>& uvs, const unsigned char* tex_data, int tex_w, int tex_h, int tex_channels); void set_texture_render_data( const std::vector>& tex_pixels_rgb, const std::vector& tex_widths, const std::vector& tex_heights, const std::vector, 3>>& face_uvs, const std::vector& face_tex_ids); void set_painted_mesh_data( const std::vector>& vertices, const std::vector>& indices); void set_face_colors(const std::vector>& face_colors); void set_original_face_colors(const std::vector>& face_colors); void set_filament_color_map(const std::map, std::array>& color_map); void set_render_mode(RenderMode mode); RenderMode get_render_mode() const { return m_mode; } void set_computing_overlay(bool show); void reset_view(); private: void on_paint(wxPaintEvent& evt); void on_size(wxSizeEvent& evt); void on_mouse(wxMouseEvent& evt); void ensure_gl_ready(); void render(); void render_mesh(); void render_textured_original(); void render_reset_overlay(const wxSize& logical_size, const wxSize& viewport_size); void upload_reset_icon_textures(); unsigned int upload_reset_icon_texture(const std::string& icon_name); wxRect reset_overlay_rect() const; bool handle_reset_overlay_mouse(wxMouseEvent& evt); void upload_textures(); void compute_smooth_normals(); void update_bounding_box(); wxGLContext* m_context = nullptr; bool m_gl_initialized = false; RenderMode m_mode = RenderMode::Original; float m_zoom = 1.0f; float m_rot_x = -30.0f; float m_rot_y = 30.0f; float m_pan_x = 0.0f; float m_pan_y = 0.0f; wxPoint m_last_mouse_pos; enum class DragMode { None, Rotate, Pan }; DragMode m_drag_mode = DragMode::None; std::vector> m_vertices; std::vector> m_indices; std::vector> m_uvs; std::vector> m_painted_vertices; std::vector> m_painted_indices; std::vector> m_face_colors_rgb; std::vector> m_original_face_colors_rgb; std::vector> m_filament_colors_rgb; std::map, std::array> m_color_map; unsigned int m_tex_id = 0; int m_tex_w = 0; int m_tex_h = 0; int m_tex_channels = 3; bool m_tex_dirty = false; std::vector m_tex_data; std::vector m_gl_tex_ids; std::vector> m_tex_pixels_rgb; std::vector m_tex_widths; std::vector m_tex_heights; std::vector, 3>> m_face_uvs; std::vector m_face_tex_ids; bool m_multi_tex_dirty = false; std::vector> m_vertex_normals; std::array m_center = {0, 0, 0}; float m_radius = 1.0f; unsigned int m_reset_icon_tex = 0; unsigned int m_reset_icon_hover_tex = 0; unsigned int m_reset_icon_dark_tex = 0; unsigned int m_reset_icon_dark_hover_tex = 0; bool m_reset_overlay_hovered = false; bool m_reset_overlay_pressed = false; }; class TextureImportDialog : public DPIDialog { public: TextureImportDialog(wxWindow* parent, const Slic3r::TexturedMesh& textured_mesh, const std::vector& filament_entries, std::function initial_cancel_callback = {}, std::function initial_progress_callback = {}); ~TextureImportDialog(); int ShowModal() override; void on_dpi_changed(const wxRect& suggested_rect) override; Slic3r::PaintedMesh get_painted_mesh() const; std::vector get_matches() const; bool was_skipped() const { return m_skipped; } bool fallback_to_geometry_only() const { return m_fallback_to_geometry_only; } // Colors of virtual filaments that need to be created after dialog confirmation. // Index i corresponds to filament index (m_existing_filament_count + i). const std::vector>& get_new_filament_colors() const { return m_new_filament_colors; } const std::vector& get_new_filament_preset_names() const { return m_new_filament_preset_names; } const std::vector& get_new_mixed_filaments() const { return m_new_mixed_filaments; } const std::vector& get_filament_entries() const { return m_filament_entries; } size_t get_existing_filament_count() const { return m_existing_filament_count; } private: void build_ui(); void build_preview_panel(wxWindow* parent, wxSizer* sizer); void build_params_panel(wxWindow* parent, wxSizer* sizer); void build_mapping_panel(wxWindow* parent, wxSizer* sizer); void build_bottom_buttons(wxSizer* sizer); void set_state(TextureImportState new_state); void update_ui_for_state(); void start_computation(bool auto_color = false, bool initial = false); void cancel_computation(); void on_computation_complete(wxCommandEvent& evt); void on_computation_progress(wxCommandEvent& evt); void on_computation_error(wxCommandEvent& evt); void on_mesh_repair_decision_required(wxCommandEvent& evt); void rebuild_mapping_rows(); void do_auto_match(); // Reorder m_current_matches into a canonical, predictable order (ascending // filament_index, with unmapped entries pushed to the end). Used right // after the initial computation so the first view the user sees has a // stable, intuitive layout. void sort_current_matches_by_filament_index(); // Reorder m_current_matches so they appear in the same order as // `previous_matches` (keyed by cluster_index). Entries whose cluster_index // was not present before are appended at the end, preserving their current // relative order. Used when the user toggles auto-merge so the rows do not // visually jump around. Assumes each cluster_index appears at most once in // both vectors (this invariant is currently guaranteed by do_auto_match, // which produces one match per cluster). void restore_current_match_order(const std::vector& previous_matches); std::vector build_matches_from_rows() const; void update_filament_color_map(); void show_filament_popup(size_t row_index); void dismiss_filament_popup(); void dismiss_filament_popup_on_wheel(wxMouseEvent& evt); void show_auto_mix_popup(); void dismiss_auto_mix_popup(); void set_auto_mix_mode(TextureAutoMixMode mode); void apply_auto_standard_mix(TextureAutoMixMode mode); void reset_auto_mix(); void update_auto_mix_reset_visibility(); bool add_decomposed_mixed_filament(size_t row_index); int add_virtual_filament(const std::array& rgba, const std::string& hex, const std::string& preset_name = std::string()); int add_virtual_mixed_filament(const std::string& color_hex, const std::vector& component_dialog_indices, const std::vector& ratios); size_t max_filament_count() const; bool can_add_virtual_filament() const; // Recomputes m_drop_warning_label visibility from m_filaments_dropped and // m_state. Safe to call whether or not the label has been created yet. // Visibility reflects ONLY the result of the most recent do_auto_match(): // if the latest match did not drop any cluster, the label is hidden even // if a previous match had dropped (no historical accumulation). void update_drop_warning_visibility(); void compact_used_virtual_filaments(); int find_closest_filament_index(const std::array& color) const; // Returns a vector indexed by dialog_index whose value is the 1-based // display number that mirrors the final sidebar ordering produced by // apply_textured_mesh_import_result (Plater.cpp): ExistingPhysical, // NewPhysical, ExistingMixed, NewMixed. Used so the dialog shows the // same IDs the sidebar will show after OK, instead of the raw // dialog_index + 1 (which interleaves physicals and mixeds). // MUST mirror ordering in apply_textured_mesh_import_result (Plater.cpp:9896). std::vector compute_display_numbers() const; void on_color_preset_clicked(wxCommandEvent& evt); void on_color_slider_changed(wxCommandEvent& evt); void on_color_spin_changed(wxCommandEvent& evt); void on_color_spin_text_changed(wxCommandEvent& evt); void on_smooth_slider_changed(wxCommandEvent& evt); void on_smooth_spin_changed(wxCommandEvent& evt); void on_smooth_spin_text_changed(wxCommandEvent& evt); void on_apply_clicked(wxCommandEvent& evt); void on_auto_merge_toggled(wxCommandEvent& evt); void highlight_view_button(int view_index); void on_skip_clicked(wxCommandEvent& evt); void on_ok_clicked(wxCommandEvent& evt); void set_color_count_value(int value, bool update_spin); void set_smooth_value(int value, bool update_spin); void preview_spin_text_value(SpinInput* spin, AccentSlider* slider, int& param, int min_value, int max_value, const wxString& text, std::function on_value_changed = {}); void update_color_count_preset_buttons(); bool has_valid_result() const; bool is_params_dirty() const; void update_confirm_button_state(); void style_confirm_button(bool dirty); Slic3r::TexturedMesh m_textured_mesh; std::vector m_filament_color_strs; // existing + virtual std::vector m_filament_names; // existing + virtual std::vector> m_filament_colors_rgba; // existing + virtual std::vector m_filament_entries; // aligned with m_filament_colors_rgba size_t m_existing_filament_count = 0; std::vector> m_new_filament_colors; // only virtual (to be created) std::vector m_new_filament_preset_names; // only virtual, aligned with m_new_filament_colors std::vector m_new_mixed_filaments; std::string m_default_virtual_filament_preset_name; TextureImportState m_state = TextureImportState::Idle; bool m_skipped = false; bool m_fallback_to_geometry_only = false; // True iff *the most recent* do_auto_match() ran into the global filament // limit and had to drop one or more clusters. Reset to false on every // do_auto_match() entry so it never accumulates across runs: a run that // does not drop anything must observe false here, regardless of whether // previous runs dropped. Drives the inline orange warning above the // bottom buttons; never affects the mapping itself. bool m_filaments_dropped = false; bool m_auto_merge_enabled = true; TextureAutoMixMode m_auto_mix_mode = TextureAutoMixMode::CMYW; int m_auto_mix_font_point_size = 10; Slic3r::PaintedMesh m_painted; std::vector m_current_matches; std::unique_ptr m_worker; std::atomic m_cancel_flag{false}; std::mutex m_result_mutex; Slic3r::PaintedMesh m_pending_result; std::function m_initial_cancel_callback; std::function m_initial_progress_callback; bool m_current_computation_initial = false; bool m_initial_computation_pending = false; bool m_initial_computation_cancelled = false; bool m_initial_computation_failed = false; bool m_initial_tooltips_set = false; bool m_current_computation_auto_color = false; Slic3r::TexturePaintingSettings::MeshRepairDecision m_mesh_repair_decision = Slic3r::TexturePaintingSettings::MeshRepairDecision::Ask; Button* m_btn_color_4 = nullptr; Button* m_btn_color_8 = nullptr; Button* m_btn_color_16 = nullptr; Button* m_btn_color_auto = nullptr; AccentSlider* m_color_slider = nullptr; SpinInput* m_color_spin = nullptr; AccentSlider* m_smooth_slider = nullptr; SpinInput* m_smooth_spin = nullptr; Button* m_btn_apply = nullptr; wxCheckBox* m_auto_merge_cb = nullptr; Button* m_btn_auto_mix = nullptr; Button* m_btn_mix_reset = nullptr; bool m_auto_mix_applied = false; AutoMixSelectPopup* m_auto_mix_popup = nullptr; wxScrolledWindow* m_mapping_scroll = nullptr; wxBoxSizer* m_mapping_sizer = nullptr; std::vector m_mapping_rows; FilamentSelectPopup* m_filament_popup = nullptr; int m_filament_popup_row = -1; int m_skip_next_filament_popup_row = -1; TexturePreviewCanvas* m_preview_canvas = nullptr; wxPanel* m_tab_panel = nullptr; Button* m_btn_view_original = nullptr; Button* m_btn_view_multicolor = nullptr; ProgressDialog* m_progress_dlg = nullptr; Button* m_btn_skip = nullptr; Button* m_btn_ok = nullptr; wxStaticText* m_drop_warning_label = nullptr; int m_param_color_count = 4; int m_param_smooth = 5; int m_applied_color_count = -1; int m_applied_smooth = -1; wxStaticText* m_hint_label = nullptr; static const int ID_COLOR_4 = wxID_HIGHEST + 200; static const int ID_COLOR_8 = wxID_HIGHEST + 201; static const int ID_COLOR_16 = wxID_HIGHEST + 202; static const int ID_COLOR_AUTO = wxID_HIGHEST + 203; static const int ID_BTN_APPLY = wxID_HIGHEST + 204; static const int ID_BTN_SKIP = wxID_HIGHEST + 205; static const int ID_VIEW_ORIGINAL = wxID_HIGHEST + 206; static const int ID_VIEW_MULTICOLOR = wxID_HIGHEST + 207; wxDECLARE_EVENT_TABLE(); }; }} // namespace Slic3r::GUI