Files
OrcaSlicer/src/slic3r/GUI/TextureImportDialog.hpp
T

408 lines
18 KiB
C++

#pragma once
#include "GUI_Utils.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "libslic3r/TexturePainting.hpp"
#include <wx/sizer.h>
#include <wx/stattext.h>
#include "Widgets/PopupWindow.hpp"
#include <wx/panel.h>
#include <wx/scrolwin.h>
#include <wx/textctrl.h>
#include "Widgets/SpinInput.hpp"
#include <wx/checkbox.h>
#include <wx/button.h>
#include "Widgets/Button.hpp"
#include <wx/glcanvas.h>
#include <wx/event.h>
#include <array>
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include <string>
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<unsigned int> mixed_components;
std::vector<int> mixed_ratios;
};
struct TextureNewMixedFilament {
int dialog_index{-1};
std::string color_hex;
std::vector<int> component_dialog_indices;
std::vector<int> ratios;
};
struct FilamentMappingRow {
int cluster_id = -1;
std::array<std::size_t, 3> 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<std::array<float, 3>>& vertices,
const std::vector<std::array<int, 3>>& indices);
void set_texture_data(
const std::vector<std::array<float, 2>>& uvs,
const unsigned char* tex_data, int tex_w, int tex_h, int tex_channels);
void set_texture_render_data(
const std::vector<std::vector<unsigned char>>& tex_pixels_rgb,
const std::vector<int>& tex_widths,
const std::vector<int>& tex_heights,
const std::vector<std::array<std::array<float,2>, 3>>& face_uvs,
const std::vector<int>& face_tex_ids);
void set_painted_mesh_data(
const std::vector<std::array<float, 3>>& vertices,
const std::vector<std::array<int, 3>>& indices);
void set_face_colors(const std::vector<std::array<std::size_t, 3>>& face_colors);
void set_original_face_colors(const std::vector<std::array<std::size_t, 3>>& face_colors);
void set_filament_color_map(const std::map<std::array<std::size_t, 3>, std::array<float, 3>>& 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<std::array<float, 3>> m_vertices;
std::vector<std::array<int, 3>> m_indices;
std::vector<std::array<float, 2>> m_uvs;
std::vector<std::array<float, 3>> m_painted_vertices;
std::vector<std::array<int, 3>> m_painted_indices;
std::vector<std::array<float, 3>> m_face_colors_rgb;
std::vector<std::array<float, 3>> m_original_face_colors_rgb;
std::vector<std::array<float, 3>> m_filament_colors_rgb;
std::map<std::array<std::size_t, 3>, std::array<float, 3>> 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<unsigned char> m_tex_data;
std::vector<unsigned int> m_gl_tex_ids;
std::vector<std::vector<unsigned char>> m_tex_pixels_rgb;
std::vector<int> m_tex_widths;
std::vector<int> m_tex_heights;
std::vector<std::array<std::array<float,2>, 3>> m_face_uvs;
std::vector<int> m_face_tex_ids;
bool m_multi_tex_dirty = false;
std::vector<std::array<float, 3>> m_vertex_normals;
std::array<float, 3> 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<TextureFilamentEntry>& filament_entries,
std::function<bool()> initial_cancel_callback = {},
std::function<bool(int)> initial_progress_callback = {});
~TextureImportDialog();
int ShowModal() override;
void on_dpi_changed(const wxRect& suggested_rect) override;
Slic3r::PaintedMesh get_painted_mesh() const;
std::vector<Slic3r::FilamentMatch> 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<std::array<float, 4>>& get_new_filament_colors() const { return m_new_filament_colors; }
const std::vector<std::string>& get_new_filament_preset_names() const { return m_new_filament_preset_names; }
const std::vector<TextureNewMixedFilament>& get_new_mixed_filaments() const { return m_new_mixed_filaments; }
const std::vector<TextureFilamentEntry>& 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<Slic3r::FilamentMatch>& previous_matches);
std::vector<Slic3r::FilamentMatch> 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<float, 4>& 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<int>& component_dialog_indices,
const std::vector<int>& 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<std::size_t, 3>& 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<int> 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<void()> 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<std::string> m_filament_color_strs; // existing + virtual
std::vector<std::string> m_filament_names; // existing + virtual
std::vector<std::array<float, 4>> m_filament_colors_rgba; // existing + virtual
std::vector<TextureFilamentEntry> m_filament_entries; // aligned with m_filament_colors_rgba
size_t m_existing_filament_count = 0;
std::vector<std::array<float, 4>> m_new_filament_colors; // only virtual (to be created)
std::vector<std::string> m_new_filament_preset_names; // only virtual, aligned with m_new_filament_colors
std::vector<TextureNewMixedFilament> 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<Slic3r::FilamentMatch> m_current_matches;
std::unique_ptr<std::thread> m_worker;
std::atomic<bool> m_cancel_flag{false};
std::mutex m_result_mutex;
Slic3r::PaintedMesh m_pending_result;
std::function<bool()> m_initial_cancel_callback;
std::function<bool(int)> 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<FilamentMappingRow> 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