mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
Remap filament for pre-colored models (#10303)
* Add a new feature to allow users to remap filament for a pre-painted model. * Fix the color issues to support the theme * clean up code * Fix broken freetype-2.12.1.tar.gz link
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <cstddef>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define EXPENSIVE_DEBUG_CHECKS
|
||||
@@ -1256,6 +1258,22 @@ void TriangleSelector::garbage_collect()
|
||||
m_free_vertices_head = -1;
|
||||
}
|
||||
|
||||
void TriangleSelector::remap_triangle_state(const EnforcerBlockerStateMap& state_map)
|
||||
{
|
||||
if (m_triangles.empty())
|
||||
return;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_triangles.size()), [this, &state_map](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i != range.end(); ++i) {
|
||||
Triangle& tr = m_triangles[i];
|
||||
if (tr.valid()) {
|
||||
const auto current_state = static_cast<size_t>(tr.get_state());
|
||||
tr.set_state(state_map[current_state]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TriangleSelector::TriangleSelector(const TriangleMesh& mesh, float edge_limit)
|
||||
: m_mesh{mesh}, m_neighbors(its_face_neighbors(mesh.its)), m_face_normals(its_face_normals(mesh.its)), m_edge_limit(edge_limit)
|
||||
{
|
||||
|
||||
@@ -37,6 +37,9 @@ enum class EnforcerBlockerType : int8_t {
|
||||
ExtruderMax = Extruder16
|
||||
};
|
||||
|
||||
// Type alias for the state mapping array to improve code readability
|
||||
using EnforcerBlockerStateMap = std::array<EnforcerBlockerType, (size_t)EnforcerBlockerType::ExtruderMax + 1>;
|
||||
|
||||
// Following class holds information about selected triangles. It also has power
|
||||
// to recursively subdivide the triangles and make the selection finer.
|
||||
class TriangleSelector
|
||||
@@ -344,6 +347,10 @@ public:
|
||||
// Remove all unnecessary data.
|
||||
void garbage_collect();
|
||||
|
||||
// Orca: remap the state of triangles according to the state_map
|
||||
void remap_triangle_state(const EnforcerBlockerStateMap& state_map);
|
||||
|
||||
|
||||
// Store the division trees in compact form (a long stream of bits for each triangle of the original mesh).
|
||||
// First vector contains pairs of (triangle index, first bit in the second vector).
|
||||
TriangleSplittingData serialize() const;
|
||||
@@ -416,10 +423,17 @@ protected:
|
||||
// or index of a vertex shared by the two split edges (for number_of_splits == 2).
|
||||
// For number_of_splits == 3, special_side_idx is always zero.
|
||||
char special_side_idx { 0 };
|
||||
EnforcerBlockerType state;
|
||||
bool m_selected_by_seed_fill : 1;
|
||||
// Is this triangle valid or marked to be removed?
|
||||
bool m_valid : 1;
|
||||
|
||||
// Orca:
|
||||
// IMPORTANT: `state` is intentionally placed after all other small members
|
||||
// to prevent compilers from packing it in a way that would create
|
||||
// data races during parallel processing. A write to `state` could
|
||||
// otherwise become a non-atomic read-modify-write on a memory word
|
||||
// that also contains other (bit-field) members, causing race conditions.
|
||||
EnforcerBlockerType state;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
|
||||
@@ -89,8 +89,13 @@ static std::vector<int> get_extruder_id_for_volumes(const ModelObject &model_obj
|
||||
|
||||
void GLGizmoMmuSegmentation::init_extruders_data()
|
||||
{
|
||||
m_extruders_colors = get_extruders_colors();
|
||||
m_extruders_colors = get_extruders_colors();
|
||||
m_selected_extruder_idx = 0;
|
||||
|
||||
// keep remap table consistent with current extruder count
|
||||
m_extruder_remap.resize(m_extruders_colors.size());
|
||||
for (size_t i = 0; i < m_extruder_remap.size(); ++i)
|
||||
m_extruder_remap[i] = i;
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_init()
|
||||
@@ -137,6 +142,11 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["toggle_wireframe_caption"] = _L("Alt + Shift + Enter");
|
||||
m_desc["toggle_wireframe"] = _L("Toggle Wireframe");
|
||||
|
||||
// Filament remapping descriptions
|
||||
m_desc["perform_remap"] = _L("Remap filaments");
|
||||
m_desc["remap"] = _L("Remap");
|
||||
m_desc["cancel_remap"] = _L("Cancel");
|
||||
|
||||
init_extruders_data();
|
||||
|
||||
return true;
|
||||
@@ -349,7 +359,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
|
||||
const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.f);
|
||||
const float remap_btn_width = m_imgui->calc_text_size(m_desc.at("perform_remap")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = remove_btn_width + filter_btn_width + remap_btn_width + m_imgui->scaled(2.f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
const float color_button_width = m_imgui->calc_text_size(std::string_view{""}).x + m_imgui->scaled(1.75f);
|
||||
|
||||
@@ -437,9 +448,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
ImGui::SameLine(button_offset + (button_size.x - label_size.x) / 2.f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {10.0,15.0});
|
||||
if (gray * 255.f < 80.f)
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), item_text.c_str());
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", item_text.c_str());
|
||||
else
|
||||
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), item_text.c_str());
|
||||
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), "%s", item_text.c_str());
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
@@ -674,6 +685,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
if (m_imgui->button(m_desc.at("perform_remap"))) {
|
||||
m_show_filament_remap_ui = !m_show_filament_remap_ui;
|
||||
if (m_show_filament_remap_ui) {
|
||||
// reset remap to identity on opening
|
||||
m_extruder_remap.resize(m_extruders_colors.size());
|
||||
for (size_t i = 0; i < m_extruder_remap.size(); ++i)
|
||||
m_extruder_remap[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Render filament swap UI if enabled
|
||||
if (m_show_filament_remap_ui) {
|
||||
ImGui::Separator();
|
||||
render_filament_remap_ui(window_width, max_tooltip_width);
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
|
||||
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
|
||||
show_tooltip_information(caption_max, x, get_cur_y);
|
||||
@@ -928,4 +958,205 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::render_filament_remap_ui(float window_width, float max_tooltip_width)
|
||||
{
|
||||
size_t n_extr = std::min((size_t)EnforcerBlockerType::ExtruderMax, m_extruders_colors.size());
|
||||
|
||||
const ImVec2 max_label_size = ImGui::CalcTextSize("99", NULL, true);
|
||||
const ImVec2 button_size(max_label_size.x + m_imgui->scaled(0.5f), 0.f);
|
||||
|
||||
for (int src = 0; src < (int)n_extr; ++src) {
|
||||
const ColorRGBA &src_col = m_extruders_colors[src]; // keep for text contrast
|
||||
const ColorRGBA &dst_col = m_extruders_colors[m_extruder_remap[src]];
|
||||
ImVec4 col_vec = ImGuiWrapper::to_ImVec4(dst_col);
|
||||
|
||||
if (src) ImGui::SameLine();
|
||||
std::string btn_id = "##remap_src_" + std::to_string(src);
|
||||
|
||||
ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs |
|
||||
ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (m_selected_extruder_idx != src) flags |= ImGuiColorEditFlags_NoBorder;
|
||||
|
||||
#ifdef __APPLE__
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGuiWrapper::COL_ORCA);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0);
|
||||
bool clicked = ImGui::ColorButton(btn_id.c_str(), col_vec, flags, button_size);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(1);
|
||||
#else
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGuiWrapper::COL_ORCA);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0);
|
||||
bool clicked = ImGui::ColorButton(btn_id.c_str(), col_vec, flags, button_size);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(1);
|
||||
#endif
|
||||
|
||||
// overlay destination number with proper contrast calculation
|
||||
std::string dst_txt = std::to_string(m_extruder_remap[src] + 1);
|
||||
float gray = 0.299f * dst_col.r() + 0.587f * dst_col.g() + 0.114f * dst_col.b();
|
||||
ImVec2 txt_sz = ImGui::CalcTextSize(dst_txt.c_str());
|
||||
ImVec2 pos = ImGui::GetItemRectMin();
|
||||
ImVec2 size = ImGui::GetItemRectSize();
|
||||
|
||||
if (gray * 255.f < 80.f)
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(pos.x + (size.x - txt_sz.x) * 0.5f, pos.y + (size.y - txt_sz.y) * 0.5f),
|
||||
IM_COL32(255,255,255,255), dst_txt.c_str());
|
||||
else
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(pos.x + (size.x - txt_sz.x) * 0.5f, pos.y + (size.y - txt_sz.y) * 0.5f),
|
||||
IM_COL32(0,0,0,255), dst_txt.c_str());
|
||||
|
||||
// popup with possible destinations
|
||||
std::string pop_id = "popup_" + std::to_string(src);
|
||||
if (clicked) {
|
||||
// Calculate popup position centered below the current button
|
||||
ImVec2 button_pos = ImGui::GetItemRectMin();
|
||||
ImVec2 button_size = ImGui::GetItemRectSize();
|
||||
ImVec2 popup_pos(button_pos.x + button_size.x * 0.5f, button_pos.y + button_size.y);
|
||||
|
||||
// Set popup styling BEFORE opening popup
|
||||
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Appearing, ImVec2(0.5f, -0.1f));
|
||||
ImGui::SetNextWindowBgAlpha(1.0f); // Ensure full opacity
|
||||
ImGui::OpenPopup(pop_id.c_str());
|
||||
}
|
||||
|
||||
// Apply popup styling before BeginPopup using standard Orca colors
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, m_is_dark_mode ? ImGuiWrapper::COL_WINDOW_BG_DARK : ImGuiWrapper::COL_WINDOW_BG);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, m_is_dark_mode ? ImVec4(0.5f, 0.5f, 0.5f, 1.0f) : ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
|
||||
if (ImGui::BeginPopup(pop_id.c_str())) {
|
||||
|
||||
for (int dst = 0; dst < (int)n_extr; ++dst) {
|
||||
const ColorRGBA &dst_col_popup = m_extruders_colors[dst];
|
||||
ImVec4 dst_vec = ImGuiWrapper::to_ImVec4(dst_col_popup);
|
||||
if (dst) ImGui::SameLine();
|
||||
std::string dst_btn = "##dst_" + std::to_string(src) + "_" + std::to_string(dst);
|
||||
|
||||
// Apply same styling to destination buttons
|
||||
ImGuiColorEditFlags dst_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs |
|
||||
ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
// Show border for currently selected destination filament
|
||||
if (m_extruder_remap[src] != dst) dst_flags |= ImGuiColorEditFlags_NoBorder;
|
||||
|
||||
#ifdef __APPLE__
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGuiWrapper::COL_ORCA);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0);
|
||||
bool dst_clicked = ImGui::ColorButton(dst_btn.c_str(), dst_vec, dst_flags, button_size);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(1);
|
||||
#else
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGuiWrapper::COL_ORCA);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0);
|
||||
bool dst_clicked = ImGui::ColorButton(dst_btn.c_str(), dst_vec, dst_flags, button_size);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(1);
|
||||
#endif
|
||||
|
||||
// overlay destination number on popup buttons
|
||||
std::string dst_num_txt = std::to_string(dst + 1);
|
||||
float dst_gray = 0.299f * dst_col_popup.r() + 0.587f * dst_col_popup.g() + 0.114f * dst_col_popup.b();
|
||||
ImVec2 dst_txt_sz = ImGui::CalcTextSize(dst_num_txt.c_str());
|
||||
ImVec2 dst_pos = ImGui::GetItemRectMin();
|
||||
ImVec2 dst_size = ImGui::GetItemRectSize();
|
||||
|
||||
if (dst_gray * 255.f < 80.f)
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(dst_pos.x + (dst_size.x - dst_txt_sz.x) * 0.5f, dst_pos.y + (dst_size.y - dst_txt_sz.y) * 0.5f),
|
||||
IM_COL32(255,255,255,255), dst_num_txt.c_str());
|
||||
else
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(dst_pos.x + (dst_size.x - dst_txt_sz.x) * 0.5f, dst_pos.y + (dst_size.y - dst_txt_sz.y) * 0.5f),
|
||||
IM_COL32(0,0,0,255), dst_num_txt.c_str());
|
||||
|
||||
if (dst_clicked)
|
||||
{
|
||||
m_extruder_remap[src] = dst;
|
||||
// update the source button color immediately
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// Clean up popup styling (always pop, whether popup was open or not)
|
||||
ImGui::PopStyleColor(2); // PopupBg and Border
|
||||
ImGui::PopStyleVar(2); // PopupRounding and PopupBorderSize
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.3f));
|
||||
|
||||
if (m_imgui->button(m_desc.at("remap"))) {
|
||||
remap_filament_assignments();
|
||||
m_show_filament_remap_ui = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button(m_desc.at("cancel_remap")))
|
||||
m_show_filament_remap_ui = false;
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::remap_filament_assignments()
|
||||
{
|
||||
if (m_extruder_remap.empty())
|
||||
return;
|
||||
|
||||
constexpr size_t MAX_EBT = (size_t)EnforcerBlockerType::ExtruderMax;
|
||||
EnforcerBlockerStateMap state_map;
|
||||
|
||||
// identity mapping by default
|
||||
for (size_t i = 0; i <= MAX_EBT; ++i)
|
||||
state_map[i] = static_cast<EnforcerBlockerType>(i);
|
||||
|
||||
size_t n_extr = std::min(m_extruder_remap.size(), MAX_EBT);
|
||||
const int start_extruder = (int) EnforcerBlockerType::Extruder1;
|
||||
bool any_change = false;
|
||||
for (size_t src = 0; src < n_extr; ++src) {
|
||||
size_t dst = m_extruder_remap[src];
|
||||
if (dst != src) {
|
||||
state_map[src+start_extruder] = static_cast<EnforcerBlockerType>(dst+start_extruder);
|
||||
if (src == 0)
|
||||
state_map[0] = static_cast<EnforcerBlockerType>(dst + start_extruder);
|
||||
|
||||
any_change = true;
|
||||
}
|
||||
}
|
||||
if (!any_change)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
|
||||
"Remap filament assignments",
|
||||
UndoRedo::SnapshotType::GizmoAction);
|
||||
|
||||
bool updated = false;
|
||||
int idx = -1;
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
if (!mo) return;
|
||||
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
if (!mv->is_model_part()) continue;
|
||||
++idx;
|
||||
TriangleSelectorGUI* ts = m_triangle_selectors[idx].get();
|
||||
if (!ts) continue;
|
||||
ts->remap_triangle_state(state_map);
|
||||
ts->request_update_render_data(true);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(
|
||||
_L("Filament remapping finished.").ToStdString());
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -111,6 +111,10 @@ protected:
|
||||
// BBS
|
||||
wchar_t m_current_tool = 0;
|
||||
bool m_detect_geometry_edge = true;
|
||||
|
||||
// Filament remap feature
|
||||
std::vector<size_t> m_extruder_remap; // index → target extruder index
|
||||
bool m_show_filament_remap_ui = false;
|
||||
|
||||
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
|
||||
|
||||
@@ -132,6 +136,10 @@ private:
|
||||
// BBS
|
||||
void update_triangle_selectors_colors();
|
||||
void init_extruders_data();
|
||||
|
||||
// Filament remapping methods
|
||||
void remap_filament_assignments();
|
||||
void render_filament_remap_ui(float window_width, float max_tooltip_width);
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
|
||||
Reference in New Issue
Block a user