diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 0bb5361a167..e9087d2f3e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -76,6 +76,7 @@ constexpr float SlopeAutoPaintMaxAngleDeg = 180.f; constexpr float ProjectionTextRasterBaseFontSize = 200.f; constexpr int ProjectionTextRasterMaxDimension = 2048; constexpr int ProjectionTextRasterMinFontSize = 18; +constexpr double ProjectionSliderDoubleClickMaxSeconds = 0.24; static bool queue_color_data_crash_backup(ModelObject *object) { @@ -2954,10 +2955,137 @@ struct ProjectionContext uint32_t image_width = 0; uint32_t image_height = 0; float image_opacity = 1.f; + float overlay_rotation_deg = 0.f; bool apply_transparency_as_background = false; ClippingPlane section_clipping_plane; }; +struct ProjectionScreenBounds +{ + float left = 0.f; + float top = 0.f; + float right = 0.f; + float bottom = 0.f; +}; + +static float normalize_projection_rotation_deg(float rotation_deg) +{ + if (!std::isfinite(rotation_deg)) + return 0.f; + rotation_deg = std::fmod(rotation_deg, 360.f); + if (rotation_deg > 180.f) + rotation_deg -= 360.f; + if (rotation_deg < -180.f) + rotation_deg += 360.f; + return rotation_deg; +} + +static Vec2f rotate_projection_screen_offset(const Vec2f &offset, float rotation_deg) +{ + const float angle = Geometry::deg2rad(rotation_deg); + const float c = std::cos(angle); + const float s = std::sin(angle); + return Vec2f(offset.x() * c - offset.y() * s, + offset.x() * s + offset.y() * c); +} + +static Vec2f projection_overlay_center(const ProjectionContext &context) +{ + return Vec2f(context.overlay_left + context.overlay_width * 0.5f, + context.overlay_top + context.overlay_height * 0.5f); +} + +static std::array projection_overlay_screen_corners(const ProjectionContext &context) +{ + const Vec2f center = projection_overlay_center(context); + const float half_w = context.overlay_width * 0.5f; + const float half_h = context.overlay_height * 0.5f; + const std::array offsets = { + Vec2f(-half_w, -half_h), + Vec2f( half_w, -half_h), + Vec2f( half_w, half_h), + Vec2f(-half_w, half_h) + }; + + std::array corners; + const float rotation_deg = normalize_projection_rotation_deg(context.overlay_rotation_deg); + for (size_t idx = 0; idx < offsets.size(); ++idx) + corners[idx] = center + rotate_projection_screen_offset(offsets[idx], rotation_deg); + return corners; +} + +static ProjectionScreenBounds projection_overlay_screen_bounds(const ProjectionContext &context) +{ + ProjectionScreenBounds bounds; + const std::array corners = projection_overlay_screen_corners(context); + bounds.left = std::min({ corners[0].x(), corners[1].x(), corners[2].x(), corners[3].x() }); + bounds.top = std::min({ corners[0].y(), corners[1].y(), corners[2].y(), corners[3].y() }); + bounds.right = std::max({ corners[0].x(), corners[1].x(), corners[2].x(), corners[3].x() }); + bounds.bottom = std::max({ corners[0].y(), corners[1].y(), corners[2].y(), corners[3].y() }); + return bounds; +} + +static bool projection_screen_to_image_uv(const ProjectionContext &context, + const Vec2f &screen, + Vec2f &uv, + bool reject_outside) +{ + if (context.overlay_width <= EPSILON || + context.overlay_height <= EPSILON || + context.image_width == 0 || + context.image_height == 0) + return false; + + const Vec2f center = projection_overlay_center(context); + const Vec2f unrotated = rotate_projection_screen_offset(screen - center, -normalize_projection_rotation_deg(context.overlay_rotation_deg)); + uv.x() = (unrotated.x() + context.overlay_width * 0.5f) / context.overlay_width; + uv.y() = (unrotated.y() + context.overlay_height * 0.5f) / context.overlay_height; + if (!std::isfinite(uv.x()) || !std::isfinite(uv.y())) + return false; + if (reject_outside) { + constexpr float edge_epsilon = 1e-6f; + if (uv.x() < -edge_epsilon || + uv.y() < -edge_epsilon || + uv.x() > 1.f + edge_epsilon || + uv.y() > 1.f + edge_epsilon) + return false; + uv.x() = std::clamp(uv.x(), 0.f, 1.f); + uv.y() = std::clamp(uv.y(), 0.f, 1.f); + } + return true; +} + +static bool projection_screen_to_image_pixel(const ProjectionContext &context, + const Vec2f &screen, + Vec2f &image_pixel, + bool reject_outside) +{ + Vec2f uv = Vec2f::Zero(); + if (!projection_screen_to_image_uv(context, screen, uv, reject_outside)) + return false; + image_pixel.x() = uv.x() * float(context.image_width); + image_pixel.y() = uv.y() * float(context.image_height); + return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y()); +} + +static bool projection_slider_short_double_clicked(double &last_click_time, bool click_changed_value) +{ + if (!ImGui::IsItemHovered() || !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + return false; + + if (click_changed_value) { + last_click_time = -1.0; + return false; + } + + const double now = ImGui::GetTime(); + const bool double_clicked = + last_click_time >= 0.0 && + now - last_click_time <= ProjectionSliderDoubleClickMaxSeconds; + last_click_time = double_clicked ? -1.0 : now; + return double_clicked; +} + static bool projection_section_view_active(const ProjectionContext &context) { return context.section_clipping_plane.is_active(); @@ -3695,15 +3823,11 @@ static std::optional projected_image_color_at_point(const ProjectionC Vec2f screen = Vec2f::Zero(); if (!project_point_to_screen(context, world_point, screen)) return std::nullopt; - if (screen.x() < context.overlay_left || - screen.y() < context.overlay_top || - screen.x() > context.overlay_left + context.overlay_width || - screen.y() > context.overlay_top + context.overlay_height) - return std::nullopt; - const float u = (screen.x() - context.overlay_left) / context.overlay_width; - const float v = (screen.y() - context.overlay_top) / context.overlay_height; - return sample_rgba_bilinear_clamped(*context.image_rgba, context.image_width, context.image_height, u, v); + Vec2f uv = Vec2f::Zero(); + if (!projection_screen_to_image_uv(context, screen, uv, true)) + return std::nullopt; + return sample_rgba_bilinear_clamped(*context.image_rgba, context.image_width, context.image_height, uv.x(), uv.y()); } static std::vector projected_raw_offsets_at_point(const ProjectionContext &context, @@ -3721,15 +3845,11 @@ static std::vector projected_raw_offsets_at_point(const ProjectionConte Vec2f screen = Vec2f::Zero(); if (!project_point_to_screen(context, world_point, screen)) return {}; - if (screen.x() < context.overlay_left || - screen.y() < context.overlay_top || - screen.x() > context.overlay_left + context.overlay_width || - screen.y() > context.overlay_top + context.overlay_height) - return {}; - const float u = (screen.x() - context.overlay_left) / context.overlay_width; - const float v = (screen.y() - context.overlay_top) / context.overlay_height; - return sample_raw_offsets_bilinear_clamped(atlas, u, v); + Vec2f uv = Vec2f::Zero(); + if (!projection_screen_to_image_uv(context, screen, uv, true)) + return {}; + return sample_raw_offsets_bilinear_clamped(atlas, uv.x(), uv.y()); } static bool projection_triangle_intersects_overlay(const ProjectionContext &context, @@ -3750,19 +3870,22 @@ static bool projection_triangle_intersects_overlay(const ProjectionContext Vec2f screen = Vec2f::Zero(); if (!project_point_to_depth_clipped_screen(context, vertex, screen)) continue; - min_x = std::min(min_x, screen.x()); - min_y = std::min(min_y, screen.y()); - max_x = std::max(max_x, screen.x()); - max_y = std::max(max_y, screen.y()); + Vec2f image_pixel = Vec2f::Zero(); + if (!projection_screen_to_image_pixel(context, screen, image_pixel, false)) + continue; + min_x = std::min(min_x, image_pixel.x()); + min_y = std::min(min_y, image_pixel.y()); + max_x = std::max(max_x, image_pixel.x()); + max_y = std::max(max_y, image_pixel.y()); any_projected = true; } if (!any_projected) return false; - return max_x >= context.overlay_left && - min_x <= context.overlay_left + context.overlay_width && - max_y >= context.overlay_top && - min_y <= context.overlay_top + context.overlay_height; + return max_x >= 0.f && + min_x <= float(context.image_width) && + max_y >= 0.f && + min_y <= float(context.image_height); } static bool project_point_to_image_pixel(const ProjectionContext &context, @@ -3779,9 +3902,7 @@ static bool project_point_to_image_pixel(const ProjectionContext &context, if (!project_point_to_screen(context, world_point, screen)) return false; - image_pixel.x() = ((screen.x() - context.overlay_left) / context.overlay_width) * float(context.image_width); - image_pixel.y() = ((screen.y() - context.overlay_top) / context.overlay_height) * float(context.image_height); - return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y()); + return projection_screen_to_image_pixel(context, screen, image_pixel, false); } static bool project_depth_clipped_point_to_image_pixel(const ProjectionContext &context, @@ -3798,9 +3919,7 @@ static bool project_depth_clipped_point_to_image_pixel(const ProjectionContext & if (!project_point_to_depth_clipped_screen(context, world_point, screen)) return false; - image_pixel.x() = ((screen.x() - context.overlay_left) / context.overlay_width) * float(context.image_width); - image_pixel.y() = ((screen.y() - context.overlay_top) / context.overlay_height) * float(context.image_height); - return std::isfinite(image_pixel.x()) && std::isfinite(image_pixel.y()); + return projection_screen_to_image_pixel(context, screen, image_pixel, false); } static float projection_triangle_image_pixel_span(const ProjectionContext &context, @@ -3885,28 +4004,27 @@ static float projection_triangle_overlay_image_pixel_span(const ProjectionContex polygon.reserve(world_polygon.size()); for (const Vec3d &vertex : world_polygon) { Vec2f screen = Vec2f::Zero(); - if (project_point_to_depth_clipped_screen(context, vertex, screen)) - polygon.emplace_back(screen); + if (!project_point_to_depth_clipped_screen(context, vertex, screen)) + continue; + Vec2f image_pixel = Vec2f::Zero(); + if (projection_screen_to_image_pixel(context, screen, image_pixel, false)) + polygon.emplace_back(image_pixel); } if (polygon.size() < 2) return projection_triangle_image_pixel_span(context, world_matrix, vertices); - projection_clip_screen_polygon(polygon, 0, context.overlay_left, true); - projection_clip_screen_polygon(polygon, 0, context.overlay_left + context.overlay_width, false); - projection_clip_screen_polygon(polygon, 1, context.overlay_top, true); - projection_clip_screen_polygon(polygon, 1, context.overlay_top + context.overlay_height, false); + projection_clip_screen_polygon(polygon, 0, 0.f, true); + projection_clip_screen_polygon(polygon, 0, float(context.image_width), false); + projection_clip_screen_polygon(polygon, 1, 0.f, true); + projection_clip_screen_polygon(polygon, 1, float(context.image_height), false); if (polygon.size() < 2) return projection_triangle_image_pixel_span(context, world_matrix, vertices); float span = 0.f; for (size_t i = 0; i + 1 < polygon.size(); ++i) { - const Vec2f pixel_i(((polygon[i].x() - context.overlay_left) / context.overlay_width) * float(context.image_width), - ((polygon[i].y() - context.overlay_top) / context.overlay_height) * float(context.image_height)); for (size_t j = i + 1; j < polygon.size(); ++j) { - const Vec2f pixel_j(((polygon[j].x() - context.overlay_left) / context.overlay_width) * float(context.image_width), - ((polygon[j].y() - context.overlay_top) / context.overlay_height) * float(context.image_height)); - span = std::max(span, (pixel_i - pixel_j).norm()); + span = std::max(span, (polygon[i] - polygon[j]).norm()); } } return span; @@ -5016,12 +5134,18 @@ static ProjectionVisibility build_projection_visibility(const ProjectionContext if (object == nullptr || context.overlay_width <= 0.f || context.overlay_height <= 0.f) return visibility; - const float max_dim = std::max(context.overlay_width, context.overlay_height); + const ProjectionScreenBounds bounds = projection_overlay_screen_bounds(context); + const float bounds_width = bounds.right - bounds.left; + const float bounds_height = bounds.bottom - bounds.top; + if (bounds_width <= 0.f || bounds_height <= 0.f) + return visibility; + + const float max_dim = std::max(bounds_width, bounds_height); visibility.scale = max_dim > 2048.f ? 2048.f / max_dim : 1.f; - visibility.width = std::max(1, int(std::ceil(context.overlay_width * visibility.scale))); - visibility.height = std::max(1, int(std::ceil(context.overlay_height * visibility.scale))); - visibility.left = context.overlay_left; - visibility.top = context.overlay_top; + visibility.width = std::max(1, int(std::ceil(bounds_width * visibility.scale))); + visibility.height = std::max(1, int(std::ceil(bounds_height * visibility.scale))); + visibility.left = bounds.left; + visibility.top = bounds.top; visibility.depth.assign(size_t(visibility.width) * size_t(visibility.height), std::numeric_limits::max()); visibility.triangle_keys.assign(visibility.depth.size(), PROJECTION_VISIBILITY_INVALID_TRIANGLE_KEY); @@ -14802,8 +14926,17 @@ void GLGizmoImageProjection::on_load(cereal::BinaryInputArchive& ar) m_text_capitalize = true; } + try { + ar(m_projection_rotation_deg, + m_projection_panel_expanded); + } catch (...) { + m_projection_rotation_deg = 0.f; + m_projection_panel_expanded = true; + } + m_projection_mode = ProjectionMode(std::clamp(mode, 0, 2)); m_projection_mode_initialized = false; + m_projection_rotation_deg = normalize_projection_rotation_deg(m_projection_rotation_deg); m_text_font_size = ProjectionTextRasterBaseFontSize; for (float &channel : m_text_color) channel = std::clamp(channel, 0.f, 1.f); @@ -14883,6 +15016,9 @@ void GLGizmoImageProjection::on_save(cereal::BinaryOutputArchive& ar) const m_text_background_color[2], m_text_background_transparent, m_text_capitalize); + + ar(m_projection_rotation_deg, + m_projection_panel_expanded); } void GLGizmoImageProjection::on_render() @@ -15053,6 +15189,7 @@ void GLGizmoImageProjection::mark_text_projection_dirty() m_text_raw_atlas = {}; if (m_text_mode) m_overlay_texture_dirty = true; + show_projection_overlay(); m_parent.set_as_dirty(); m_parent.request_extra_frame(); } @@ -15277,6 +15414,46 @@ bool GLGizmoImageProjection::ensure_overlay_texture() return true; } +void GLGizmoImageProjection::render_projection_overlay() +{ + if (!m_show_overlay || !ensure_overlay_texture()) + return; + + const OverlayRect rect = overlay_rect(); + if (rect.width <= 0.f || rect.height <= 0.f) + return; + + ProjectionContext overlay_context; + overlay_context.overlay_left = rect.left; + overlay_context.overlay_top = rect.top; + overlay_context.overlay_width = rect.width; + overlay_context.overlay_height = rect.height; + overlay_context.image_width = active_projection_width(); + overlay_context.image_height = active_projection_height(); + overlay_context.overlay_rotation_deg = m_projection_rotation_deg; + const std::array corners = projection_overlay_screen_corners(overlay_context); + ImGui::GetBackgroundDrawList()->AddImageQuad((void *)(intptr_t)m_overlay_texture.get_id(), + ImVec2(corners[0].x(), corners[0].y()), + ImVec2(corners[1].x(), corners[1].y()), + ImVec2(corners[2].x(), corners[2].y()), + ImVec2(corners[3].x(), corners[3].y()), + ImVec2(0.f, 0.f), + ImVec2(1.f, 0.f), + ImVec2(1.f, 1.f), + ImVec2(0.f, 1.f), + ImGui::GetColorU32( + ImVec4(1.f, 1.f, 1.f, 0.72f * std::clamp(m_projection_opacity, 0.f, 1.f)))); +} + +void GLGizmoImageProjection::show_projection_overlay() +{ + if (m_show_overlay) + return; + m_show_overlay = true; + m_parent.set_as_dirty(); + m_parent.request_extra_frame(); +} + GLGizmoImageProjection::OverlayRect GLGizmoImageProjection::overlay_rect() const { OverlayRect rect; @@ -15454,6 +15631,30 @@ bool GLGizmoImageProjection::selected_object_has_raw_atlas_texture_data() const return false; } +bool GLGizmoImageProjection::render_projection_action_controls() +{ + if (active_projection_empty()) { + bool overlay = false; + m_imgui->disabled_begin(true); + ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &overlay); + m_imgui->disabled_end(); + } else if (ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &m_show_overlay)) { + m_parent.set_as_dirty(); + m_parent.request_extra_frame(); + } + + m_imgui->disabled_begin(active_projection_empty() || !projection_mode_allowed(m_projection_mode)); + const wxString project_label = m_text_mode ? _L("Project text onto model") : _L("Project image onto model"); + const bool projected = m_imgui->button(project_label) && project_image_to_selected_object(); + if (projected) { + m_show_overlay = false; + m_parent.set_as_dirty(); + m_parent.request_extra_frame(); + } + m_imgui->disabled_end(); + return projected; +} + void GLGizmoImageProjection::on_render_input_window(float x, float y, float bottom_limit) { if (m_text_mode) @@ -15469,6 +15670,20 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); const float max_tooltip_width = ImGui::GetFontSize() * 20.f; + if (m_imgui->button(m_projection_panel_expanded ? _L("Collapse") : _L("Expand"))) { + m_projection_panel_expanded = !m_projection_panel_expanded; + m_parent.set_as_dirty(); + m_parent.request_extra_frame(); + } + + if (!m_projection_panel_expanded) { + render_projection_action_controls(); + GizmoImguiEnd(); + ImGuiWrapper::pop_toolbar_style(); + render_projection_overlay(); + return; + } + if (m_imgui->button(_L("Manage Color Data for this object"))) open_color_data_management_dialog(); @@ -15498,6 +15713,7 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott if (m_text_mode) ensure_text_projection_image(); update_default_projection_mode(); + show_projection_overlay(); m_parent.set_as_dirty(); m_parent.request_extra_frame(); } @@ -15518,11 +15734,6 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott m_imgui->warning_text(raw_offset_data_rgba_conversion_warning_text(), m_imgui->scaled(RawOffsetDataWarningWrapEm)); } - if (!active_projection_empty() && ImGui::Checkbox(m_text_mode ? "Show text overlay" : "Show image overlay", &m_show_overlay)) { - m_parent.set_as_dirty(); - m_parent.request_extra_frame(); - } - // if (has_rgba_data) // m_imgui->warning_text(_L("Note: Image Texture mode is disabled as this object has RGBA data (must use RGBA data mode)")); @@ -15539,6 +15750,7 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott if (ImGui::Selectable(mode_labels[idx], selected, flags) && allowed) { mode = idx; m_projection_mode = candidate; + show_projection_overlay(); } if (selected) ImGui::SetItemDefaultFocus(); @@ -15549,10 +15761,39 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott float opacity_pct = m_projection_opacity * 100.f; m_imgui->text(_L("Opacity")); ImGui::PushItemWidth(m_imgui->scaled(8.f)); - if (m_imgui->bbl_slider_float_style("##image_projection_opacity", &opacity_pct, 0.f, 100.f, "%.0f%%", 1.f, true)) { - m_projection_opacity = std::clamp(opacity_pct / 100.f, 0.f, 1.f); + const bool opacity_changed = + m_imgui->bbl_slider_float_style("##image_projection_opacity", &opacity_pct, 0.f, 100.f, "%.0f%%", 1.f, true); + const bool opacity_reset = projection_slider_short_double_clicked(m_projection_opacity_last_click_time, opacity_changed); + if (opacity_reset) + m_projection_opacity_reset_active = true; + const bool suppress_opacity_slider = opacity_reset || m_projection_opacity_reset_active; + if (suppress_opacity_slider || opacity_changed) { + m_projection_opacity = suppress_opacity_slider ? 1.f : std::clamp(opacity_pct / 100.f, 0.f, 1.f); + show_projection_overlay(); m_parent.set_as_dirty(); + m_parent.request_extra_frame(); } + if (m_projection_opacity_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left)) + m_projection_opacity_reset_active = false; + ImGui::PopItemWidth(); + + float rotation_deg = m_projection_rotation_deg; + m_imgui->text(_L("Rotation")); + ImGui::PushItemWidth(m_imgui->scaled(8.f)); + const bool rotation_changed = + m_imgui->bbl_slider_float_style("##image_projection_rotation", &rotation_deg, -180.f, 180.f, "%.0f deg", 1.f, true); + const bool rotation_reset = projection_slider_short_double_clicked(m_projection_rotation_last_click_time, rotation_changed); + if (rotation_reset) + m_projection_rotation_reset_active = true; + const bool suppress_rotation_slider = rotation_reset || m_projection_rotation_reset_active; + if (suppress_rotation_slider || rotation_changed) { + m_projection_rotation_deg = suppress_rotation_slider ? 0.f : normalize_projection_rotation_deg(rotation_deg); + show_projection_overlay(); + m_parent.set_as_dirty(); + m_parent.request_extra_frame(); + } + if (m_projection_rotation_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left)) + m_projection_rotation_reset_active = false; ImGui::PopItemWidth(); if (m_c != nullptr && m_c->object_clipper() != nullptr) { @@ -15564,21 +15805,36 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott if (m_c != nullptr && m_c->object_clipper() != nullptr) m_c->object_clipper()->set_position_by_ratio(-1., false); }); + show_projection_overlay(); } float clp_dist = float(m_c->object_clipper()->get_position()); ImGui::PushItemWidth(m_imgui->scaled(8.f)); - if (m_imgui->bbl_slider_float_style("##image_projection_clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.f, true)) + const bool section_changed = + m_imgui->bbl_slider_float_style("##image_projection_clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.f, true); + const bool section_reset = projection_slider_short_double_clicked(m_projection_section_last_click_time, section_changed); + if (section_reset) + m_projection_section_reset_active = true; + const bool suppress_section_slider = section_reset || m_projection_section_reset_active; + if (suppress_section_slider) { + m_c->object_clipper()->set_position_by_ratio(0., true); + show_projection_overlay(); + } else if (section_changed) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); + show_projection_overlay(); + } + if (m_projection_section_reset_active && !ImGui::IsMouseDown(ImGuiMouseButton_Left)) + m_projection_section_reset_active = false; ImGui::PopItemWidth(); if (ImGui::IsItemHovered()) m_imgui->tooltip(_L("Section view"), max_tooltip_width); } - if (!m_text_mode) - ImGui::Checkbox("Apply transparent regions as background color", &m_apply_transparency_as_background); - ImGui::Checkbox("Pass through model", &m_pass_through_model); + if (!m_text_mode && ImGui::Checkbox("Apply transparent regions as background color", &m_apply_transparency_as_background)) + show_projection_overlay(); + if (ImGui::Checkbox("Pass through model", &m_pass_through_model)) + show_projection_overlay(); const bool show_raw_conversion_option = active_raw_atlas_valid() && @@ -15587,30 +15843,12 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott if (show_raw_conversion_option) ImGui::Checkbox("Convert existing colors to raw offset data (slow)", &m_convert_existing_colors_to_raw_offsets); - m_imgui->disabled_begin(active_projection_empty() || !projection_mode_allowed(m_projection_mode)); - const wxString project_label = m_text_mode ? _L("Project text onto model") : _L("Project image onto model"); - if (m_imgui->button(project_label) && project_image_to_selected_object()) { - m_show_overlay = false; - m_parent.set_as_dirty(); - m_parent.request_extra_frame(); - } - m_imgui->disabled_end(); + render_projection_action_controls(); 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)))); - } - } + render_projection_overlay(); } bool GLGizmoImageProjection::project_image_to_selected_object() @@ -15708,6 +15946,7 @@ bool GLGizmoImageProjection::project_image_to_selected_object() context.image_width = active_projection_width(); context.image_height = active_projection_height(); context.image_opacity = m_projection_opacity; + context.overlay_rotation_deg = m_projection_rotation_deg; context.apply_transparency_as_background = effective_apply_transparency_as_background(); apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr); @@ -15753,6 +15992,7 @@ bool GLGizmoImageProjection::project_to_vertex_colors(ModelObject *object) context.image_width = active_projection_width(); context.image_height = active_projection_height(); context.image_opacity = m_projection_opacity; + context.overlay_rotation_deg = m_projection_rotation_deg; context.apply_transparency_as_background = effective_apply_transparency_as_background(); apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr); @@ -15904,6 +16144,7 @@ bool GLGizmoImageProjection::project_to_image_texture(ModelObject *object) context.image_width = active_projection_width(); context.image_height = active_projection_height(); context.image_opacity = m_projection_opacity; + context.overlay_rotation_deg = m_projection_rotation_deg; context.apply_transparency_as_background = effective_apply_transparency_as_background(); apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr); @@ -16223,6 +16464,7 @@ bool GLGizmoImageProjection::project_to_rgb_data(ModelObject *object) context.image_width = active_projection_width(); context.image_height = active_projection_height(); context.image_opacity = m_projection_opacity; + context.overlay_rotation_deg = m_projection_rotation_deg; context.apply_transparency_as_background = effective_apply_transparency_as_background(); apply_projection_section_view(context, m_c != nullptr ? m_c->object_clipper() : nullptr); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 752edc67357..8a418357b72 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -522,6 +522,9 @@ private: bool ensure_text_projection_image(); bool update_text_raw_atlas(const std::vector &glyph_mask); void render_text_projection_controls(float max_tooltip_width); + bool render_projection_action_controls(); + void render_projection_overlay(); + void show_projection_overlay(); bool ensure_overlay_texture(); OverlayRect overlay_rect() const; const std::vector &active_projection_rgba() const; @@ -579,6 +582,14 @@ private: bool m_overlay_texture_dirty = false; bool m_show_overlay = true; float m_projection_opacity = 1.f; + bool m_projection_opacity_reset_active = false; + double m_projection_opacity_last_click_time = -1.0; + float m_projection_rotation_deg = 0.f; + bool m_projection_rotation_reset_active = false; + double m_projection_rotation_last_click_time = -1.0; + bool m_projection_section_reset_active = false; + double m_projection_section_last_click_time = -1.0; + bool m_projection_panel_expanded = true; bool m_apply_transparency_as_background = false; bool m_pass_through_model = false; bool m_convert_existing_colors_to_raw_offsets = true;