From a1ad2b44253929eee183e6429f0693c21e8428af Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 2 Oct 2026 11:50:43 -0300 Subject: [PATCH] Add section view feature for 3D canvas (#15879) --- docs/HLSD/section-view.md | 103 ++++++ resources/images/canvas_section.svg | 1 + resources/images/canvas_section_active.svg | 1 + .../images/canvas_section_active_dark.svg | 1 + .../canvas_section_active_dark_hover.svg | 1 + .../images/canvas_section_active_hover.svg | 1 + resources/images/canvas_section_dark.svg | 1 + .../images/canvas_section_dark_hover.svg | 1 + resources/images/canvas_section_hover.svg | 1 + src/libvgcode/include/Viewer.hpp | 4 + src/libvgcode/src/Shaders.hpp | 65 +++- src/libvgcode/src/ShadersES.hpp | 71 ++++- src/libvgcode/src/Viewer.cpp | 5 + src/libvgcode/src/ViewerImpl.cpp | 6 + src/libvgcode/src/ViewerImpl.hpp | 7 + src/slic3r/GUI/GCodeViewer.cpp | 9 + src/slic3r/GUI/GCodeViewer.hpp | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 299 ++++++++++++++---- src/slic3r/GUI/GLCanvas3D.hpp | 28 ++ src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp | 42 +-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 39 +-- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 47 +-- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 36 +-- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 14 - src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 35 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 188 +---------- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 123 +------ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 58 ++-- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 18 +- src/slic3r/GUI/KBShortcutsDialog.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 1 + 31 files changed, 599 insertions(+), 612 deletions(-) create mode 100644 docs/HLSD/section-view.md create mode 100644 resources/images/canvas_section.svg create mode 100644 resources/images/canvas_section_active.svg create mode 100644 resources/images/canvas_section_active_dark.svg create mode 100644 resources/images/canvas_section_active_dark_hover.svg create mode 100644 resources/images/canvas_section_active_hover.svg create mode 100644 resources/images/canvas_section_dark.svg create mode 100644 resources/images/canvas_section_dark_hover.svg create mode 100644 resources/images/canvas_section_hover.svg diff --git a/docs/HLSD/section-view.md b/docs/HLSD/section-view.md new file mode 100644 index 0000000000..d37a09db00 --- /dev/null +++ b/docs/HLSD/section-view.md @@ -0,0 +1,103 @@ +# Section view — High Level Design + +## Purpose and scope + +Section view hides whatever lies between the camera and a plane, so the user can look inside +objects in Prepare and in the assembly view, and inside the toolpaths in Preview. It is a view +setting: it changes nothing in the model, the slice or the project file, and it does not reach +plate thumbnails. + +The user controls it from the section button of the canvas toolbar in the bottom left corner of +the 3D view. The button opens a panel above it with a slider for the depth of the cut, a "Set +viewing angle" button that turns the plane to face the camera at the same depth, and a button that +resets the depth to zero. The panel is an ordinary overlay window, not a popup, so the scene keeps +taking clicks and drags while it is open; the button or Esc closes it again. Esc closes the panel +before it closes a gizmo or clears the selection. The button is highlighted +while a section cuts the scene and has shortcuts of its own: the mouse wheel over it moves the +plane, a right click switches the section off and back on, and a middle click sets the viewing +angle. Alt + mouse wheel moves the plane anywhere in the 3D view, with or without a gizmo open. + +## State + +Prepare and Preview share one section view, so a cut made in either tab is the same cut in the +other. The assembly view, whose objects sit apart from their places on the plate, and the Design +tab keep their own. The section itself is two values. + +- **Ratio**, from 0 to 1. At 0 the section is off. As the ratio grows, the plane sweeps the + sphere around the objects, from its side facing the camera to the opposite side, so at 1 + everything is cut away. +- **Normal**, taken from the camera direction the first time the section is switched on, and + again whenever the user sets the viewing angle. The plane keeps that orientation while the + camera orbits and while the section is off, so the cut face can be seen from any side and + bringing the depth back to 0 does not lose the angle. + +The ratio in use when the section is switched off is kept, and the right click on the button +brings the section back at that ratio, which restores the same cut. Whether the panel is open is +shared along with the section. + +The sphere is recomputed every frame from the volumes of the canvas the section view belongs to: +the objects on the current plate, or every object when that plate is empty. Preview holds no +objects of its own, so it places the plane across the volumes of Prepare, which makes it cut the +toolpaths exactly where Prepare cuts the objects. Only G-code opened on its own, without objects, +is measured by its toolpaths. In the assembly view the sphere is around the whole assembly. The +ratio therefore keeps its meaning when objects move or the user switches plates. It is not a +fixed position in world space. + +Only the tab on screen can change the section, and switching tabs redraws the whole scene and +closes the open gizmo, so neither tab ever shows a stale cut. + +## Where the plane applies + +`GLCanvas3D::_get_section_view_plane()` turns the state into a plane in the convention of +`ClippingPlane::is_point_clipped()`. Everything that draws or picks the scene reads that plane. + +- **Volumes.** The plane goes to the `clipping_plane` uniform of the volume shaders. The same + uniform serves the gouraud, phong and X-ray passes and the colour picking pass. +- **Cut faces.** Clipping only discards fragments, which would leave the cut volumes hollow. + `_render_section_view_caps()` draws their cut faces with one `MeshClipper` per model part the + plane passes through. A clipper recomputes its face only when the plane or the volume moves. + Modifiers, the wipe tower and SLA auxiliaries get no face. +- **Toolpaths.** libvgcode takes the plane through `Viewer::set_clipping_plane()`. It draws each + extrusion as only the faces of a diamond-section prism that turn towards the camera, so + discarding the fragments on the clipped side would leave open shells. Instead, the segment + shader follows the view ray from a fragment that is cut away to the plane. When the + extrusion's diamond section still holds that point, the fragment is shaded as the cut face, lit + as the plane faces; otherwise it is discarded. The cut face keeps the depth of the fragment it + replaces, which is safe: along that ray everything else still shown lies behind the plane. The + shader writes no `gl_FragDepth`, so early depth testing survives. Option markers are cut away + whole, by their centres. The shadow casters share the segment shader, so what is cut away casts + no shadow either. Preview shells are drawn by another shader and are not clipped. +- **Picking.** `get_raycaster_clipping_plane()` returns the same plane, so hover, selection and + the perspective pan anchor ignore what the user cannot see. + +## Gizmos + +A gizmo that clips its object itself owns the gizmo data pool's `ObjectClipper`, and its plane +replaces the canvas section while the gizmo is open. `GLGizmosManager::get_clipping_plane()` +reports that plane, or nothing when no open gizmo has a clipper. There are two cases. + +- **Painting tools and brim ears** show the canvas section on the object they edit. + `GLGizmosManager::update_section_view()` copies the ratio and normal into their clipper + whenever the pool is updated or the section changes. The clipper then places the plane across + the edited instance, which is the only object shown. The painting tools keep clipping their + own triangles, raycasts and cut face through it. Brim ears always cut horizontally from the + top, because the ears sit on the plate. At ratio 0 the clipper holds no plane at all, so the + raycasts are not clipped. +- **Cut and mesh boolean** use the clipper for their own purposes, so the canvas section is + suspended while they are open. + +Every other gizmo, including move, rotate and scale, leaves the canvas section in place. + +## Alt + mouse wheel + +The canvas handles Alt + wheel after the gizmos had their turn, so it works the same in every +tab and with any gizmo open. On Windows, releasing Alt when no key was pressed since it went down +opens the window menu, and a wheel turn does not count as a key. Under the custom title bar that +menu is invisible, yet it takes the keyboard and the next click, which looks like a frozen 3D +view. After Alt + wheel the canvas therefore consumes the Alt release instead of passing it on. + +## Redraw + +The button and the panel are part of the ImGui overlay, which is built after the frame's scene is +drawn. A change to the section therefore marks the scene dirty and asks for one more frame. The +cached scene is never reused across a change. diff --git a/resources/images/canvas_section.svg b/resources/images/canvas_section.svg new file mode 100644 index 0000000000..553c01b22c --- /dev/null +++ b/resources/images/canvas_section.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_active.svg b/resources/images/canvas_section_active.svg new file mode 100644 index 0000000000..1d8351730b --- /dev/null +++ b/resources/images/canvas_section_active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_active_dark.svg b/resources/images/canvas_section_active_dark.svg new file mode 100644 index 0000000000..45cc5a09c6 --- /dev/null +++ b/resources/images/canvas_section_active_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_active_dark_hover.svg b/resources/images/canvas_section_active_dark_hover.svg new file mode 100644 index 0000000000..9472dd39cd --- /dev/null +++ b/resources/images/canvas_section_active_dark_hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_active_hover.svg b/resources/images/canvas_section_active_hover.svg new file mode 100644 index 0000000000..8664ba71d9 --- /dev/null +++ b/resources/images/canvas_section_active_hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_dark.svg b/resources/images/canvas_section_dark.svg new file mode 100644 index 0000000000..3f5660f889 --- /dev/null +++ b/resources/images/canvas_section_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_dark_hover.svg b/resources/images/canvas_section_dark_hover.svg new file mode 100644 index 0000000000..b0d38d38e5 --- /dev/null +++ b/resources/images/canvas_section_dark_hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/canvas_section_hover.svg b/resources/images/canvas_section_hover.svg new file mode 100644 index 0000000000..6bd98eda77 --- /dev/null +++ b/resources/images/canvas_section_hover.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 179105032d..7eadc16de0 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -76,6 +76,10 @@ public: // caller decides which of the two it varies with the realistic view setting. // void set_tone(float exposure, float saturation); + // + // ORCA: section view. Cuts away where dot(plane, (x, y, z, 1)) < 0; { 0, 0, 0, 1 } keeps all. + // + void set_clipping_plane(const std::array& plane); // // ************************************************************************ diff --git a/src/libvgcode/src/Shaders.hpp b/src/libvgcode/src/Shaders.hpp index 9f546d6dc8..b4980885b6 100644 --- a/src/libvgcode/src/Shaders.hpp +++ b/src/libvgcode/src/Shaders.hpp @@ -38,6 +38,8 @@ static const char* Segments_Vertex_Shader = // rejection discards most of the hidden fragments; set when the camera looks down on the print "uniform int reverse_order;\n" "uniform int instance_count;\n" +// ORCA: section view +"uniform vec4 clipping_plane;\n" "in int vertex_id;\n" "out vec3 color;\n" "// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n" @@ -48,6 +50,9 @@ static const char* Segments_Vertex_Shader = "// needs its world position and, for the depth bias, its eye space normal.\n" "out vec3 world_position;\n" "out vec3 shadow_normal;\n" +// ORCA: section view - the segment and the light on its cut face +"flat out int segment_id;\n" +"out vec3 cut_color_direct;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -159,6 +164,9 @@ static const char* Segments_Vertex_Shader = " color_direct = color_base * direct_lighting(eye_position, eye_normal);\n" " world_position = pos;\n" " shadow_normal = eye_normal;\n" +" segment_id = id_a;\n" +" cut_color_direct = clipping_plane.xyz == vec3(0.0) ? vec3(0.0) :\n" +" color_base * direct_lighting(eye_position, mat3(view_matrix) * -clipping_plane.xyz);\n" " gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" "}\n"; @@ -178,23 +186,33 @@ static const char* Segments_Fragment_Shader = // passes in each mode is decided in GLCanvas3D::_render_gcode, not here. "uniform float exposure;\n" "uniform float saturation;\n" +// ORCA: section view - only camera-facing faces are drawn, so cut faces are ray cast +"uniform vec4 clipping_plane;\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"uniform vec3 camera_position;\n" +"uniform samplerBuffer position_tex;\n" +"uniform samplerBuffer height_width_angle_tex;\n" +"const vec3 UP = vec3(0, 0, 1);\n" "const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n" "in vec3 color;\n" "in vec3 color_direct;\n" "in vec3 world_position;\n" "in vec3 shadow_normal;\n" +"flat in int segment_id;\n" +"in vec3 cut_color_direct;\n" "out vec4 fragment_color;\n" -"float shadow_shade() {\n" +"float shadow_shade(vec3 position, vec3 normal) {\n" " if (shadow_intensity <= 0.0)\n" " return 1.0;\n" -" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n" +" vec4 lp = shadow_light_vp * vec4(position, 1.0);\n" " vec3 proj = lp.xyz / lp.w;\n" " proj = proj * 0.5 + 0.5;\n" " if (proj.z > 1.0)\n" " return 1.0;\n" " // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n" " // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n" -" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n" +" float NdotL = dot(normalize(normal), SHADOW_LIGHT_DIR);\n" " float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n" " float sum = 0.0;\n" " for (int x = -2; x <= 2; ++x) {\n" @@ -205,8 +223,39 @@ static const char* Segments_Fragment_Shader = " }\n" " return 1.0 - shadow_intensity * (sum / 25.0);\n" "}\n" +// ORCA: section view - where the view ray meets the plane, if inside the extrusion's diamond section +"bool find_cut(out vec3 cut) {\n" +// parallel rays for an orthographic projection +" vec3 ray = projection_matrix[3][3] == 1.0 ? -vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]) :\n" +" normalize(world_position - camera_position);\n" +" float approach = dot(clipping_plane.xyz, ray);\n" +" if (approach <= 0.0)\n" +" return false;\n" +" cut = world_position - dot(vec4(world_position, 1.0), clipping_plane) / approach * ray;\n" +" vec3 pos_a = texelFetch(position_tex, segment_id).xyz;\n" +" vec3 line = texelFetch(position_tex, segment_id + 1).xyz - pos_a;\n" +" float length_sq = dot(line, line);\n" +" float along = length_sq < 1e-8 ? -1.0 : dot(cut - pos_a, line) / length_sq;\n" +" if (along < 0.0 || along > 1.0)\n" +" return false;\n" +" vec3 line_dir = line * inversesqrt(length_sq);\n" +" vec3 right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n" +" vec3 offset = cut - pos_a - along * line;\n" +" vec2 half_height_width = 0.5 * mix(texelFetch(height_width_angle_tex, segment_id).xy,\n" +" texelFetch(height_width_angle_tex, segment_id + 1).xy, along);\n" +" return abs(dot(offset, right_dir)) / half_height_width.y + abs(dot(offset, cross(right_dir, line_dir))) / half_height_width.x <= 1.0;\n" +"}\n" "void main() {\n" -" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n" +" vec3 c;\n" +" if (dot(vec4(world_position, 1.0), clipping_plane) >= 0.0)\n" +" c = color + color_direct * shadow_shade(world_position, shadow_normal);\n" +" else {\n" +" vec3 cut;\n" +" if (!find_cut(cut))\n" +" discard;\n" +" c = color + cut_color_direct * shadow_shade(cut, mat3(view_matrix) * -clipping_plane.xyz);\n" +" }\n" +" c *= exposure;\n" " c = mix(vec3(dot(c, LUMA)), c, saturation);\n" " fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n" "}\n"; @@ -234,9 +283,12 @@ static const char* Options_Vertex_Shader = "uniform samplerBuffer height_width_angle_tex;\n" "uniform samplerBuffer color_tex;\n" "uniform usamplerBuffer segment_index_tex;\n" +// ORCA: section view +"uniform vec4 clipping_plane;\n" "in vec3 in_position;\n" "in vec3 in_normal;\n" "out vec3 color;\n" +"out float clipping_plane_dot;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -263,6 +315,8 @@ static const char* Options_Vertex_Shader = " height_width.y, 0.0, 0.0,\n" " 0.0, height_width.y, 0.0,\n" " 0.0, 0.0, height_width.x);\n" +// ORCA: section view - markers are cut away whole, by their centre +" clipping_plane_dot = dot(vec4(offset, 1.0), clipping_plane);\n" " vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n" " // ORCA: Apply bias to z-position to avoid z-fighting\n" " eye_position.z += bias;\n" @@ -275,8 +329,11 @@ static const char* Options_Vertex_Shader = static const char* Options_Fragment_Shader = "#version 150\n" "in vec3 color;\n" +"in float clipping_plane_dot;\n" "out vec4 fragment_color;\n" "void main() {\n" +" if (clipping_plane_dot < 0.0)\n" +" discard;\n" " fragment_color = vec4(color, 1.0);\n" "}\n"; diff --git a/src/libvgcode/src/ShadersES.hpp b/src/libvgcode/src/ShadersES.hpp index 4194b836e3..1a88e18f96 100644 --- a/src/libvgcode/src/ShadersES.hpp +++ b/src/libvgcode/src/ShadersES.hpp @@ -32,6 +32,8 @@ static const char* Segments_Vertex_Shader_ES = "uniform sampler2D height_width_angle_tex;\n" "uniform sampler2D color_tex;\n" "uniform usampler2D segment_index_tex;\n" +// ORCA: section view +"uniform vec4 clipping_plane;\n" "in float vertex_id_float;\n" "out vec3 color;\n" "// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n" @@ -42,6 +44,9 @@ static const char* Segments_Vertex_Shader_ES = "// needs its world position and, for the depth bias, its eye space normal.\n" "out vec3 world_position;\n" "out vec3 shadow_normal;\n" +// ORCA: section view - the segment and the light on its cut face +"flat out int segment_id;\n" +"out vec3 cut_color_direct;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -156,12 +161,17 @@ static const char* Segments_Vertex_Shader_ES = " color_direct = color_base * direct_lighting(eye_position, eye_normal);\n" " world_position = pos;\n" " shadow_normal = eye_normal;\n" +" segment_id = id_a;\n" +" cut_color_direct = clipping_plane.xyz == vec3(0.0) ? vec3(0.0) :\n" +" color_base * direct_lighting(eye_position, mat3(view_matrix) * -clipping_plane.xyz);\n" " gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" "}\n"; static const char* Segments_Fragment_Shader_ES = "#version 300 es\n" "precision highp float;\n" +// ORCA: section view - segment ids exceed mediump +"precision highp int;\n" "// ORCA: sampler2D defaults to lowp in an ES fragment shader, far too coarse to compare\n" "// shadow map depths against.\n" "precision highp sampler2D;\n" @@ -179,23 +189,33 @@ static const char* Segments_Fragment_Shader_ES = // passes in each mode is decided in GLCanvas3D::_render_gcode, not here. "uniform float exposure;\n" "uniform float saturation;\n" +// ORCA: section view - cut faces are ray cast; data samplers stay lowp as in the vertex shader +"uniform vec4 clipping_plane;\n" +"uniform mat4 view_matrix;\n" +"uniform mat4 projection_matrix;\n" +"uniform vec3 camera_position;\n" +"uniform lowp sampler2D position_tex;\n" +"uniform lowp sampler2D height_width_angle_tex;\n" +"const vec3 UP = vec3(0, 0, 1);\n" "const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n" "in vec3 color;\n" "in vec3 color_direct;\n" "in vec3 world_position;\n" "in vec3 shadow_normal;\n" +"flat in int segment_id;\n" +"in vec3 cut_color_direct;\n" "out vec4 fragment_color;\n" -"float shadow_shade() {\n" +"float shadow_shade(vec3 position, vec3 normal) {\n" " if (shadow_intensity <= 0.0)\n" " return 1.0;\n" -" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n" +" vec4 lp = shadow_light_vp * vec4(position, 1.0);\n" " vec3 proj = lp.xyz / lp.w;\n" " proj = proj * 0.5 + 0.5;\n" " if (proj.z > 1.0)\n" " return 1.0;\n" " // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n" " // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n" -" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n" +" float NdotL = dot(normalize(normal), SHADOW_LIGHT_DIR);\n" " float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n" " float sum = 0.0;\n" " for (int x = -2; x <= 2; ++x) {\n" @@ -206,8 +226,43 @@ static const char* Segments_Fragment_Shader_ES = " }\n" " return 1.0 - shadow_intensity * (sum / 25.0);\n" "}\n" +"vec4 fetch(lowp sampler2D sampler, int id) {\n" +" ivec2 tex_size = textureSize(sampler, 0);\n" +" return texelFetch(sampler, (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x), 0);\n" +"}\n" +// ORCA: section view - where the view ray meets the plane, if inside the extrusion's diamond section +"bool find_cut(out vec3 cut) {\n" +// parallel rays for an orthographic projection +" vec3 ray = projection_matrix[3][3] == 1.0 ? -vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]) :\n" +" normalize(world_position - camera_position);\n" +" float approach = dot(clipping_plane.xyz, ray);\n" +" if (approach <= 0.0)\n" +" return false;\n" +" cut = world_position - dot(vec4(world_position, 1.0), clipping_plane) / approach * ray;\n" +" vec3 pos_a = fetch(position_tex, segment_id).xyz;\n" +" vec3 line = fetch(position_tex, segment_id + 1).xyz - pos_a;\n" +" float length_sq = dot(line, line);\n" +" float along = length_sq < 1e-8 ? -1.0 : dot(cut - pos_a, line) / length_sq;\n" +" if (along < 0.0 || along > 1.0)\n" +" return false;\n" +" vec3 line_dir = line * inversesqrt(length_sq);\n" +" vec3 right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n" +" vec3 offset = cut - pos_a - along * line;\n" +" vec2 half_height_width = 0.5 * mix(fetch(height_width_angle_tex, segment_id).xy,\n" +" fetch(height_width_angle_tex, segment_id + 1).xy, along);\n" +" return abs(dot(offset, right_dir)) / half_height_width.y + abs(dot(offset, cross(right_dir, line_dir))) / half_height_width.x <= 1.0;\n" +"}\n" "void main() {\n" -" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n" +" vec3 c;\n" +" if (dot(vec4(world_position, 1.0), clipping_plane) >= 0.0)\n" +" c = color + color_direct * shadow_shade(world_position, shadow_normal);\n" +" else {\n" +" vec3 cut;\n" +" if (!find_cut(cut))\n" +" discard;\n" +" c = color + cut_color_direct * shadow_shade(cut, mat3(view_matrix) * -clipping_plane.xyz);\n" +" }\n" +" c *= exposure;\n" " c = mix(vec3(dot(c, LUMA)), c, saturation);\n" " fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n" "}\n"; @@ -230,9 +285,12 @@ static const char* Options_Vertex_Shader_ES = "uniform sampler2D height_width_angle_tex;\n" "uniform sampler2D color_tex;\n" "uniform usampler2D segment_index_tex;\n" +// ORCA: section view +"uniform vec4 clipping_plane;\n" "in vec3 in_position;\n" "in vec3 in_normal;\n" "out vec3 color;\n" +"out float clipping_plane_dot;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -264,6 +322,8 @@ static const char* Options_Vertex_Shader_ES = " height_width.y, 0.0, 0.0,\n" " 0.0, height_width.y, 0.0,\n" " 0.0, 0.0, height_width.x);\n" +// ORCA: section view - markers are cut away whole, by their centre +" clipping_plane_dot = dot(vec4(offset, 1.0), clipping_plane);\n" " vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n" " vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n" " vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n" @@ -275,8 +335,11 @@ static const char* Options_Fragment_Shader_ES = "#version 300 es\n" "precision highp float;\n" "in vec3 color;\n" +"in float clipping_plane_dot;\n" "out vec4 fragment_color;\n" "void main() {\n" +" if (clipping_plane_dot < 0.0)\n" +" discard;\n" " fragment_color = vec4(color, 1.0);\n" "}\n"; diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp index 61d7310508..4d5cc29b7c 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -57,6 +57,11 @@ void Viewer::set_tone(float exposure, float saturation) m_impl->set_tone(exposure, saturation); } +void Viewer::set_clipping_plane(const std::array& plane) +{ + m_impl->set_clipping_plane(plane); +} + EViewType Viewer::get_view_type() const { return m_impl->get_view_type(); diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 8212717931..47e2d43f01 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -773,6 +773,8 @@ void ViewerImpl::init(const std::string& opengl_context_version) m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure"); m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation"); m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale"); + // ORCA: section view + m_uni_segments_clipping_plane_id = glGetUniformLocation(m_segments_shader_id, "clipping_plane"); glcheck(); assert(m_uni_segments_view_matrix_id != -1 && m_uni_segments_projection_matrix_id != -1 && @@ -797,6 +799,8 @@ void ViewerImpl::init(const std::string& opengl_context_version) m_uni_options_height_width_angle_tex_id = glGetUniformLocation(m_options_shader_id, "height_width_angle_tex"); m_uni_options_colors_tex_id = glGetUniformLocation(m_options_shader_id, "color_tex"); m_uni_options_segment_index_tex_id = glGetUniformLocation(m_options_shader_id, "segment_index_tex"); + // ORCA: section view + m_uni_options_clipping_plane_id = glGetUniformLocation(m_options_shader_id, "clipping_plane"); glcheck(); assert(m_uni_options_view_matrix_id != -1 && m_uni_options_projection_matrix_id != -1 && @@ -2110,6 +2114,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure)); glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation)); glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f)); + glsafe(glUniform4fv(m_uni_segments_clipping_plane_id, 1, m_clipping_plane.data())); glsafe(glDisable(GL_CULL_FACE)); @@ -2197,6 +2202,7 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project glsafe(glUniform1i(m_uni_options_segment_index_tex_id, 3)); glsafe(glUniformMatrix4fv(m_uni_options_view_matrix_id, 1, GL_FALSE, view_matrix.data())); glsafe(glUniformMatrix4fv(m_uni_options_projection_matrix_id, 1, GL_FALSE, projection_matrix.data())); + glsafe(glUniform4fv(m_uni_options_clipping_plane_id, 1, m_clipping_plane.data())); glsafe(glEnable(GL_CULL_FACE)); diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index 707f0f6bb7..bf8f8558d9 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -89,6 +89,8 @@ public: // caller decides which of the two it varies with the realistic view setting. // void set_tone(float exposure, float saturation); + // ORCA: section view, see Viewer::set_clipping_plane() + void set_clipping_plane(const std::array& plane) { m_clipping_plane = plane; } EViewType get_view_type() const { return m_settings.view_type; } void set_view_type(EViewType type); @@ -371,6 +373,7 @@ private: int m_uni_segments_exposure_id{ -1 }; int m_uni_segments_saturation_id{ -1 }; int m_uni_segments_bias_scale_id{ -1 }; + int m_uni_segments_clipping_plane_id{ -1 }; // // Caches for OpenGL uniforms id for options shader // @@ -380,6 +383,7 @@ private: int m_uni_options_height_width_angle_tex_id{ -1 }; int m_uni_options_colors_tex_id{ -1 }; int m_uni_options_segment_index_tex_id{ -1 }; + int m_uni_options_clipping_plane_id{ -1 }; #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS // // Caches for OpenGL uniforms id for cog marker shader @@ -530,6 +534,9 @@ private: float m_exposure{ 1.0f }; float m_saturation{ 1.0f }; + // ORCA: section view + std::array m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f }; + void apply_pending_updates(); void update_view_full_range(); void update_color_ranges(); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 04c930dcfe..dab9cf145b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1782,6 +1782,15 @@ void GCodeViewer::set_tone(float exposure, float saturation) m_viewer.set_tone(exposure, saturation); } +void GCodeViewer::set_clipping_plane(const ClippingPlane& plane) +{ + // Flipped to match ClippingPlane::distance(). + const Vec3f normal = -plane.get_normal().cast(); + m_viewer.set_clipping_plane(plane.is_active() ? + std::array{ normal.x(), normal.y(), normal.z(), float(plane.get_offset()) } : + std::array{ 0.0f, 0.0f, 0.0f, 1.0f }); +} + void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin) { if (m_viewer.get_extrusion_roles().empty()) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5154726aba..291891ccc2 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -300,6 +300,8 @@ public: // ORCA: tone applied to the shaded toolpaths, paying back the light the lighting term, // the shadow and the SSAO pass each take off. 1.0/1.0 is a no-op. void set_tone(float exposure, float saturation); + // ORCA: section view + void set_clipping_plane(const ClippingPlane& plane); //BBS // void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ea116587ac..9462f2e2ed 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1324,8 +1324,11 @@ GLCanvas3D::~GLCanvas3D() m_shadow_map_fbo = 0; } m_plate_shadow_mask.reset(); + m_section_view_caps.clear(); } m_plate_shadow_mask_key.clear(); + if (m_section_view->owner == this) + m_section_view->owner = nullptr; reset_volumes(); @@ -1474,6 +1477,7 @@ void GLCanvas3D::reset_volumes() m_selection.clear(); m_volumes.clear(); + m_section_view_caps.clear(); m_dirty = true; _set_warning_notification(EWarning::ObjectOutside, false); @@ -1624,6 +1628,42 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(ObjectFil return state; } +void GLCanvas3D::set_section_view_ratio(double ratio) +{ + ratio = std::clamp(ratio, 0., 1.); + if (ratio == m_section_view->ratio) + return; + + if (ratio == 0.) + m_section_view->last_ratio = m_section_view->ratio; + m_section_view->ratio = ratio; + // Only the first cut faces the camera; later ones keep their angle until it is set again. + if (m_section_view->normal.isZero()) + align_section_view_to_camera(); + else + _on_section_view_changed(); +} + +void GLCanvas3D::toggle_section_view() +{ + const double last_ratio = m_section_view->last_ratio; + set_section_view_ratio(is_section_view_active() ? 0. : last_ratio > 0. ? last_ratio : 0.5); +} + +void GLCanvas3D::align_section_view_to_camera() +{ + m_section_view->normal = -wxGetApp().plater()->get_camera().get_dir_forward(); + _on_section_view_changed(); +} + +void GLCanvas3D::_on_section_view_changed() +{ + m_gizmos.update_section_view(); + // Set from the overlay, after this frame's scene was drawn. + set_as_dirty(); + request_extra_frame(); +} + void GLCanvas3D::toggle_world_axes_visibility(bool force_show) { if (force_show) { @@ -2339,6 +2379,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size) } /* preview render */ else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) { + // Before the shadow pass, so the cut away part casts no shadow. + m_gcode_viewer.set_clipping_plane(_get_section_view_plane()); _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); _render_sla_slices(); _render_selection(); @@ -3121,7 +3163,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Bed); m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); m_gizmos.update_data(); - m_gizmos.update_assemble_view_data(); m_gizmos.refresh_on_off_state(); // Update the toolbar @@ -3837,12 +3878,18 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) m_key_down = { keyCode, key_repeats(keyCode) }; else if (evt.GetEventType() == wxEVT_KEY_UP) key_released(keyCode); + const bool alt_wheel_released = evt.GetEventType() == wxEVT_KEY_UP && keyCode == WXK_ALT && std::exchange(m_alt_wheel_used, false); auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) render(); else { + // Before the gizmos and the selection, which Esc also closes. + if (evt.GetEventType() == wxEVT_KEY_DOWN && keyCode == WXK_ESCAPE && std::exchange(m_section_view->panel_open, false)) { + _set_overlay_as_dirty(); + return; + } if (!m_gizmos.on_key(evt)) { if (evt.GetEventType() == wxEVT_KEY_UP) { if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) { @@ -3911,7 +3958,8 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) && keyCode != WXK_LEFT && keyCode != WXK_UP && keyCode != WXK_RIGHT - && keyCode != WXK_DOWN) { + && keyCode != WXK_DOWN + && !alt_wheel_released) { evt.Skip(); // Needed to have EVT_CHAR generated as well } } @@ -3931,6 +3979,8 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; + m_alt_wheel_used |= evt.AltDown(); + #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); @@ -3976,25 +4026,21 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (m_gizmos.on_mouse_wheel(evt)) return; - if (m_canvas_type == CanvasAssembleView && (evt.AltDown() || evt.CmdDown()) && m_gizmos.m_assemble_view_data != nullptr) { + if (evt.AltDown()) { + set_section_view_ratio(get_section_view_ratio() + (evt.GetWheelRotation() < 0 ? -0.01 : 0.01)); + return; + } + + if (m_canvas_type == CanvasAssembleView && evt.CmdDown()) { float rotation = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); - if (evt.AltDown()) { - auto clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position(); - clp_dist = rotation < 0.f - ? std::max(0., clp_dist - 0.01) - : std::min(1., clp_dist + 0.01); - m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true); - } - else if (evt.CmdDown()) { - m_explosion_ratio = rotation < 0.f - ? std::max(1., m_explosion_ratio - 0.01) - : std::min(3., m_explosion_ratio + 0.01); - if (m_explosion_ratio != GLVolume::explosion_ratio) { - for (GLVolume* volume : m_volumes.volumes) { - volume->set_bounding_boxes_as_dirty(); - } - GLVolume::explosion_ratio = m_explosion_ratio; + m_explosion_ratio = rotation < 0.f + ? std::max(1., m_explosion_ratio - 0.01) + : std::min(3., m_explosion_ratio + 0.01); + if (m_explosion_ratio != GLVolume::explosion_ratio) { + for (GLVolume* volume : m_volumes.volumes) { + volume->set_bounding_boxes_as_dirty(); } + GLVolume::explosion_ratio = m_explosion_ratio; } return; } @@ -8556,7 +8602,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with glsafe(::glEnable(GL_DEPTH_TEST)); - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + m_camera_clipping_plane = _get_volumes_clipping_plane(); if (m_picking_enabled) // Update the layer editing selection to the first object selected, update the current object maximum Z. @@ -8599,10 +8645,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with GLGizmosManager& gm = get_gizmos_manager(); GLGizmoBase* current_gizmo = gm.get_current(); - if (m_canvas_type == CanvasAssembleView) { - m_volumes.set_clipping_plane(m_gizmos.get_assemble_view_clipping_plane().get_data()); - } - else if (current_gizmo && !current_gizmo->apply_clipping_plane()) { + if (current_gizmo && !current_gizmo->apply_clipping_plane()) { m_volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data()); } else { @@ -8711,6 +8754,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with } } + _render_section_view_caps(); break; } case GLVolumeCollection::ERenderType::Transparent: @@ -8732,12 +8776,6 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with } }, partly_inside_enable, printable_heights); - if (m_canvas_type == CanvasAssembleView && m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position() > 0) { - const GLGizmosManager& gm = get_gizmos_manager(); - shader->stop_using(); - gm.render_painter_assemble_view(); - shader->start_using(); - } break; } } @@ -8758,6 +8796,42 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } +void GLCanvas3D::_render_section_view_caps() +{ + // A gizmo with its own clipper draws its own cut faces. + const ClippingPlane plane = m_gizmos.get_clipping_plane().has_value() ? ClippingPlane::ClipsNothing() : _get_section_view_plane(); + if (!plane.is_active() || m_model == nullptr) { + m_section_view_caps.clear(); + return; + } + + // Last frame's clippers are reused, so unchanged cuts are not recomputed. + std::map caps; + for (const GLVolume* volume : m_volumes.volumes) { + if (!volume->is_active || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0 || + volume->object_idx() >= int(m_model->objects.size())) + continue; + const ModelObject* object = m_model->objects[volume->object_idx()]; + if (volume->volume_idx() >= int(object->volumes.size())) + continue; + // Skip volumes the plane misses. + const BoundingBoxf3& box = volume->transformed_bounding_box(); + if (std::abs(plane.distance(box.center())) > 0.5 * box.size().dot(plane.get_normal().cwiseAbs())) + continue; + + auto node = m_section_view_caps.extract(volume); + MeshClipper& clipper = node ? caps.insert(std::move(node)).position->second : caps[volume]; + clipper.set_mesh(object->volumes[volume->volume_idx()]->mesh().its); + clipper.set_plane(plane); + clipper.set_transformation(Geometry::Transformation(volume->world_matrix())); + // No cut face below the plate. + if (m_canvas_type != CanvasAssembleView) + clipper.set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + clipper.render_cut({ 0.25f, 0.25f, 0.25f, 1.0f }); + } + m_section_view_caps = std::move(caps); +} + bool GLCanvas3D::_is_xray_view_active() const { if (m_canvas_type == ECanvasType::CanvasPreview || !wxGetApp().plater()->is_show_xray()) @@ -9846,7 +9920,31 @@ void GLCanvas3D::_render_canvas_toolbar() ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding , padding); // without padding images clipping imgui.begin(_L("Canvas Toolbar"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);// + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollWithMouse);// + + // Section view, on top so its panel opens above the toolbar. + const bool section_view = is_section_view_active(); + ImTextureID s_normal_id = m_gizmos.get_icon_texture_id(section_view ? + (m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_ACTIVE_DARK : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_ACTIVE) : + (m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_DARK : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION)); + ImTextureID s_hover_id = m_gizmos.get_icon_texture_id(section_view ? + (m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_ACTIVE_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_ACTIVE_HOVER) : + (m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_SECTION_HOVER)); + + if (ImGui::ImageButton3(s_normal_id, s_hover_id, btn_size)) + m_section_view->panel_open = !m_section_view->panel_open; + else if (ImGui::IsItemHovered()) { + if (const float wheel = ImGui::GetIO().MouseWheel; wheel != 0.f) + set_section_view_ratio(get_section_view_ratio() + (wheel < 0.f ? -0.01 : 0.01)); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + toggle_section_view(); + else if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) + align_section_view_to_camera(); + imgui.tooltip(_L("Section view"), ImGui::GetFontSize() * 20.0f); + } + const ImVec2 section_panel_pos = ImGui::GetItemRectMin() - ImVec2(0.f, spacing.y); + + ImGui::Dummy({ 0, spacing.y}); ImTextureID m_normal_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_DARK : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU); ImTextureID m_hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_HOVER); @@ -10012,6 +10110,76 @@ void GLCanvas3D::_render_canvas_toolbar() ImGui::PopStyleVar(6); imgui.end(); + + _render_section_view_panel(section_panel_pos); +} + +void GLCanvas3D::_render_section_view_panel(const ImVec2& bottom_left) +{ + if (!m_section_view->panel_open) + return; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const float sc = get_scale(); + + ImGuiWrapper::push_toolbar_style(sc); + ImGui::PushStyleColor(ImGuiCol_WindowBg, m_is_dark ? ImGuiWrapper::COL_TOOLBAR_BG_DARK : ImGuiWrapper::COL_TOOLBAR_BG); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.f * sc); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.f, 8.f) * sc); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.f, 4.f) * sc); + + ImGui::SetNextWindowPos(bottom_left, ImGuiCond_Always, ImVec2(0.f, 1.f)); + imgui.begin(std::string("##CanvasSectionView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); + + const wxString alt = GUI::shortkey_alt_prefix(); + const std::vector> tips = { + { alt + _L("Mouse wheel"), _L("Move section plane") }, + { _L("Mouse wheel on icon"), _L("Move section plane") }, + { _L("Right click on icon"), _L("Toggle section view") }, + { _L("Middle click on icon"), _L("Set viewing angle") }, + }; + // render_tooltip_button() puts the tips below y plus the panel height; they go above the panel. + const ImGuiStyle& style = ImGui::GetStyle(); + const float tips_h = tips.size() * (ImGui::GetTextLineHeight() + style.ItemSpacing.y) - style.ItemSpacing.y + 2.f * style.WindowPadding.y; + GLGizmoUtils::render_tooltip_button(&imgui, *this, tips, ImGui::GetWindowPos().x, + ImGui::GetWindowPos().y - style.ItemSpacing.y - tips_h - ImGui::GetContentRegionMax().y - ImGui::GetFrameHeight()); + + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Section view")); + + float ratio = float(get_section_view_ratio()); + ImGui::SameLine(); + ImGui::SetNextItemWidth(imgui.scaled(7.f)); + bool changed = imgui.bbl_slider_float_style("##section_view", &ratio, 0.f, 1.f, "%.2f", 1.0f, true); + ImGui::SameLine(); + ImGui::SetNextItemWidth(1.5f * imgui.get_slider_icon_size().x); + changed |= ImGui::BBLDragFloat("##section_view_input", &ratio, 0.05f, 0.0f, 0.0f, "%.2f"); + if (changed) + set_section_view_ratio(ratio); + + ImGui::SameLine(); + if (imgui.button(_L("Set viewing angle"))) + align_section_view_to_camera(); + + imgui.disabled_begin(!is_section_view_active()); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.f, 0.f, 0.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.f, 0.f, 0.f, 0.f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f); + if (imgui.button(wxString(ImGui::RevertBtn) + "##section_view_reset", _L("Reset"))) + set_section_view_ratio(0.); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + imgui.disabled_end(); + + imgui.end(); + + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(); + ImGuiWrapper::pop_toolbar_style(); } void GLCanvas3D::_render_separator_toolbar_right() const @@ -10259,17 +10427,12 @@ float GLCanvas3D::_render_assembly_tooltip_button(ImGuiWrapper* imgui_wrapper) c //BBS void GLCanvas3D::_render_assemble_control() { - if(m_gizmos.m_assemble_view_data == nullptr) - return; - if (m_canvas_type != ECanvasType::CanvasAssembleView) { GLVolume::explosion_ratio = m_explosion_ratio = 1.0; return; } - if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmSegmentation) { - m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(0.0, true); + if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmSegmentation) return; - } ImGuiWrapper* imgui = wxGetApp().imgui(); @@ -10279,8 +10442,6 @@ void GLCanvas3D::_render_assemble_control() auto canvas_h = float(get_canvas_size().get_height()); const float text_padding = 7.0f; - const float text_size_x = std::max(imgui->calc_text_size(_L("Reset direction")).x + 2 * ImGui::GetStyle().FramePadding.x, - std::max(imgui->calc_text_size(_L("Explosion Ratio")).x, imgui->calc_text_size(_L("Section View")).x)); const float slider_width = 60.0f; const float value_size = imgui->calc_text_size("3.00"sv).x + text_padding * 2; const float item_spacing = imgui->get_item_spacing().x; @@ -10295,34 +10456,6 @@ void GLCanvas3D::_render_assemble_control() tooltip_button_width = _render_assembly_tooltip_button(imgui); } float same_line_width = tooltip_button_width; - { - float clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position(); - if (clp_dist == 0.f) { - ImGui::AlignTextToFramePadding(); - imgui->text(_L("Section View")); - } - else { - if (imgui->button(_L("Reset direction"))) { - wxGetApp().CallAfter([this]() { - m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(-1., false); - }); - } - } - same_line_width += (text_size_x + item_spacing); - ImGui::SameLine(same_line_width); - ImGui::PushItemWidth(slider_width); - bool view_slider_changed = imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - - same_line_width += (slider_width + item_spacing); - ImGui::SameLine(same_line_width); - ImGui::PushItemWidth(value_size); - bool view_input_changed = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (view_slider_changed || view_input_changed) - m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true); - - same_line_width += (value_size + item_spacing * 2); - } { auto temp_x = imgui->calc_text_size(_L("Explosion Ratio")).x; ImGui::SameLine(same_line_width); @@ -10746,12 +10879,42 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) return mouse_ray(mouse_pos).intersect_plane(0.0); } +ClippingPlane GLCanvas3D::_get_section_view_plane() const +{ + const SectionView& section_view = *m_section_view; + if (section_view.ratio == 0.) + return ClippingPlane::ClipsNothing(); + + // The owner's objects, so Preview cuts where Prepare does. + const GLCanvas3D& owner = section_view.owner != nullptr ? *section_view.owner : *this; + BoundingBoxf3 box = owner.volumes_bounding_box(true); + if (!box.defined) + box = owner.volumes_bounding_box(); + // G-code without objects: its toolpaths, merged by corner as a flat box is undefined. + if (!box.defined && m_gcode_viewer.has_data()) { + box.merge(m_gcode_viewer.get_paths_bounding_box().min); + box.merge(m_gcode_viewer.get_paths_bounding_box().max); + } + if (!box.defined) + return ClippingPlane::ClipsNothing(); + + // Sweeps the bounding sphere from the camera side (0) to the far side (1), as ObjectClipper does. + const double radius = box.radius(); + return ClippingPlane(section_view.normal, section_view.normal.dot(box.center()) + radius - 2. * radius * section_view.ratio); +} + +ClippingPlane GLCanvas3D::_get_volumes_clipping_plane() const +{ + const std::optional gizmo_plane = m_gizmos.get_clipping_plane(); + return gizmo_plane.has_value() ? *gizmo_plane : _get_section_view_plane().inverted_normal(); +} + ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const { // Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and // invert the result into the convention expected by SceneRaycaster. GLGizmoBase* current_gizmo = m_gizmos.get_current(); - return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() : + return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? _get_volumes_clipping_plane() : ClippingPlane::ClipsNothing()) .inverted_normal(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f861e15ca1..9dffe195c4 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -619,6 +619,19 @@ private: std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; + struct SectionView + { + explicit SectionView(const GLCanvas3D* owner) : owner(owner) {} + double ratio{ 0. }; // 0 = off + double last_ratio{ 0. }; // before it was switched off + Vec3d normal{ Vec3d::Zero() }; // zero until first aimed + bool panel_open{ false }; + const GLCanvas3D* owner; // whose objects place the plane + }; + std::shared_ptr m_section_view{ std::make_shared(this) }; + std::map m_section_view_caps; + // Its release would open the hidden window menu on Windows. + bool m_alt_wheel_used{ false }; std::array m_sla_caps; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false @@ -943,6 +956,14 @@ public: void set_color_clip_plane(const Vec3d& cp_normal, double offset) { m_volumes.set_color_clip_plane(cp_normal, offset); } void set_color_clip_plane_colors(const std::array& colors) { m_volumes.set_color_clip_plane_colors(colors); } + bool is_section_view_active() const { return m_section_view->ratio > 0.; } + double get_section_view_ratio() const { return m_section_view->ratio; } + const Vec3d& get_section_view_normal() const { return m_section_view->normal; } + void set_section_view_ratio(double ratio); + void toggle_section_view(); + void align_section_view_to_camera(); + void share_section_view(const GLCanvas3D& owner) { m_section_view = owner.m_section_view; } + void toggle_world_axes_visibility(bool force_show = false); void refresh_camera_scene_box(); void set_color_by(const std::string& value); @@ -1392,6 +1413,7 @@ private: void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix); //BBS: add outline drawing logic void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); + void _render_section_view_caps(); void _render_wireframe_overlay(); bool _is_xray_view_active() const; void _render_xray_volumes(); @@ -1418,6 +1440,7 @@ private: void _render_assemble_view_toolbar() const; void _render_return_toolbar() const; void _render_canvas_toolbar(); + void _render_section_view_panel(const ImVec2& bottom_left); void _render_separator_toolbar_right() const; void _render_separator_toolbar_left() const; void _render_collapse_toolbar() const; @@ -1449,6 +1472,11 @@ private: Gesture }; + void _on_section_view_changed(); + // In the ClippingPlane::is_point_clipped() convention. + ClippingPlane _get_section_view_plane() const; + // In the volume shaders' convention: the open gizmo's own plane, else the section view's. + ClippingPlane _get_volumes_clipping_plane() const; // Orca: These helpers keep clipping, orbit pivots, and perspective-pan depth selection consistent. ClippingPlane get_raycaster_clipping_plane() const; bool is_bed_visible() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp index 7f52b2cc0c..944cfaad7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -85,7 +85,6 @@ bool GLGizmoBrimEars::on_init() m_shortcut = Shortcut::GizmoBrimEars; const wxString ctrl = GUI::shortkey_ctrl_prefix(); - const wxString alt = GUI::shortkey_alt_prefix(); m_desc["brim_ear_radius"] = _L("Brim ear radius"); m_desc["max_angle"] = _L("Max angle"); @@ -96,13 +95,11 @@ bool GLGizmoBrimEars::on_init() m_desc["create"] = _L("Create"); m_desc["auto_generate"] = _L("Auto-generate"); m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius"); - m_desc["section_view"] = _L("Section view"); m_shortcuts = { {_L("Left mouse button"), _L("Add or Select")}, {_L("Right mouse button"), _L("Remove")}, {ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]}, - {alt + _L("Mouse wheel"), m_desc["section_view"]}, }; return true; @@ -551,26 +548,6 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p apply_radius_change(); } - if (action == SLAGizmoEventType::MouseWheelUp && alt_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position_by_ratio(pos, false, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && alt_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position_by_ratio(pos, false, true); - return true; - } - - // reset clipper position - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position_by_ratio(-1., false); - return true; - } - return false; } @@ -692,8 +669,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); float space_size = m_imgui->get_style_scaling() * 8; - std::vector text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"], - m_desc["create"], m_desc["remove"]}; + std::vector text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["create"], + m_desc["remove"]}; float widest_text = m_imgui->find_widest_text(text_list); float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x; float input_text_size = m_imgui->scaled(10.0f); @@ -782,21 +759,6 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi } } m_imgui->disabled_end(); - - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - float clp_dist = float(m_c->object_clipper()->get_position()); - m_imgui->text(m_desc["section_view"]); - ImGui::SameLine(caption_size); - ImGui::PushItemWidth(slider_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##section_view", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { - m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); - } ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 6a6288a71c..7ed567ac33 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -112,36 +112,30 @@ bool GLGizmoFdmSupports::on_init() m_desc["highlight_by_angle"] = _L("Highlight overhangs"); m_desc["tool_type"] = _L("Tool type"); m_desc["gap_fill"] = _L("Gap fill"); - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["clipping_of_view"] = _L("Section view"); m_desc["cursor_size"] = _L("Brush size"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); m_desc["gap_area"] = _L("Gap area"); const wxString ctrl = GUI::shortkey_ctrl_prefix(); - const wxString alt = GUI::shortkey_alt_prefix(); const wxString shift = GUI::shortkey_shift_prefix(); std::pair enforce_shortcut = {_L("Left mouse button"), _L("Enforce supports")}; std::pair block_shortcut = {_L("Right mouse button"), _L("Block supports")}; std::pair remove_shortcut = {shift + _L("Left mouse button"), _L("Erase")}; - std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; m_shortcuts_brush = { enforce_shortcut, block_shortcut, remove_shortcut, - {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, - clipping_shortcut + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]} }; m_shortcuts_bucket_fill = { enforce_shortcut, block_shortcut, remove_shortcut, - {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, - clipping_shortcut + {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]} }; m_shortcuts_gap_fill = { @@ -237,11 +231,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float space_size = m_imgui->get_style_scaling() * 8; - const float clipping_slider_left = m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f); const float gap_fill_slider_left = m_imgui->calc_text_size(m_desc.at("gap_fill")).x + m_imgui->scaled(1.5f); const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f); - const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2; const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f); const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f); const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f); @@ -254,14 +246,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l float caption_max = 0.f; float total_text_max = 0.f; - for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) { + for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size"}) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(reset_button_slider_left, std::max(std::max(cursor_slider_left, clipping_slider_left), std::max(highlight_slider_left, gap_fill_slider_left))))); + const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(cursor_slider_left, std::max(highlight_slider_left, gap_fill_slider_left)))); const float slider_icon_width = m_imgui->get_slider_icon_size().x; const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -458,29 +450,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##angle_threshold_deg_input", &m_highlight_by_angle_threshold_deg, 0.05f, 0.0f, 0.0f, "%.2f"); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); - bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - - ImGui::SameLine(drag_left_width + sliders_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - ImGui::Separator(); render_tooltip_button(x, y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index 7f36e85e80..e03912456a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -51,10 +51,8 @@ bool GLGizmoFuzzySkin::on_init() m_shortcut = Shortcut::GizmoFuzzySkin; const wxString ctrl = GUI::shortkey_ctrl_prefix(); - const wxString alt = GUI::shortkey_alt_prefix(); const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["reset_direction"] = _L("Reset direction"); m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); @@ -62,7 +60,6 @@ bool GLGizmoFuzzySkin::on_init() m_desc["tool_type"] = _L("Tool type"); m_desc["tool_brush"] = _L("Brush"); m_desc["tool_smart_fill"] = _L("Smart fill"); - m_desc["clipping_of_view"] = _L("Section view"); m_desc["cursor_size"] = _L("Brush size"); m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin"); m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin"); @@ -70,26 +67,22 @@ bool GLGizmoFuzzySkin::on_init() std::pair add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]}; std::pair remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]}; - std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; m_shortcuts_brush = { add_fuzzy_skin_shortcut, remove_fuzzy_skin_shortcut, - {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, - clipping_shortcut + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]} }; m_shortcuts_triangle = { add_fuzzy_skin_shortcut, - remove_fuzzy_skin_shortcut, - clipping_shortcut + remove_fuzzy_skin_shortcut }; m_shortcuts_smart_fill = { add_fuzzy_skin_shortcut, remove_fuzzy_skin_shortcut, - {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, - clipping_shortcut + {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]} }; return true; @@ -162,8 +155,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float space_size = m_imgui->get_style_scaling() * 8; - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f), - m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f); const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f); @@ -187,9 +178,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - const float circle_max_width = std::max(clipping_slider_left, cursor_slider_left); - - const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + const float sliders_left_width = std::max(smart_fill_slider_left, cursor_slider_left); const float slider_icon_width = m_imgui->get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; const float empty_button_width = m_imgui->calc_button_size("").x; @@ -259,10 +248,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(circle_max_width); + ImGui::SameLine(cursor_slider_left); ImGui::PushItemWidth(sliders_width); m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + circle_max_width); + ImGui::SameLine(drag_left_width + cursor_slider_left); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f"); } else if (m_current_tool == ImGui::TriangleButtonIcon) { @@ -291,30 +280,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f"); } - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - - ImGui::SameLine(drag_left_width + sliders_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } - ImGui::Separator(); render_tooltip_button(x, y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 2a19cf3ee7..a08ded3003 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -118,8 +118,6 @@ bool GLGizmoMmuSegmentation::on_init() const wxString alt = GUI::shortkey_alt_prefix(); const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["clipping_of_view"] = _L("Section view"); - m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Brush size"); m_desc["cursor_type"] = _L("Brush shape"); m_desc["paint"] = _L("Paint"); @@ -146,14 +144,12 @@ bool GLGizmoMmuSegmentation::on_init() std::pair paint_shortcut = {_L("Left mouse button"), m_desc["paint"]}; std::pair erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]}; - std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; std::pair toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]}; m_shortcuts_brush = { paint_shortcut, erase_shortcut, {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, - clipping_shortcut, toggle_wireframe_shortcut }; @@ -161,7 +157,6 @@ bool GLGizmoMmuSegmentation::on_init() paint_shortcut, erase_shortcut, {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, - clipping_shortcut, toggle_wireframe_shortcut }; @@ -397,8 +392,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float space_size = m_imgui->get_style_scaling() * 8; - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f), - m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f); const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f); const float edge_detect_slider_left = m_imgui->calc_text_size(m_desc.at("edge_detection")).x + m_imgui->scaled(1.f); @@ -413,18 +406,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott float caption_max = 0.f; float total_text_max = 0.f; - for (const auto &t : std::array{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range", "clipping_of_view"}) { + for (const auto &t : std::array{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range"}) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - const float circle_max_width = std::max(clipping_slider_left,cursor_slider_left); - const float height_max_width = std::max(clipping_slider_left,height_range_slider_left); const float sliders_left_width = std::max(smart_fill_slider_left, - std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, std::max(height_range_slider_left, - clipping_slider_left))))) + space_size; + std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, height_range_slider_left)))) + space_size; const float slider_icon_width = m_imgui->get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; const int max_filament_items_per_line = 8; @@ -658,28 +648,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + sliders_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (slider_clp_dist || b_clp_dist_input) { - m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - } - ImGui::Separator(); render_tooltip_button(x, y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index c9847afcde..75d2905a86 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -750,20 +750,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } } - else if (alt_down) { - // BBS - double pos = m_c->object_clipper()->get_position(); - pos = action == SLAGizmoEventType::MouseWheelDown - ? std::max(0., pos - 0.01) - : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position_by_ratio(pos, true); - return true; - } - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position_by_ratio(-1., false); - return true; } if (action == SLAGizmoEventType::LeftDown diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 3ae5604226..1d6d31a047 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -46,11 +46,8 @@ bool GLGizmoSeam::on_init() m_shortcut = Shortcut::GizmoSeam; const wxString ctrl = GUI::shortkey_ctrl_prefix(); - const wxString alt = GUI::shortkey_alt_prefix(); const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["clipping_of_view"] = _L("Section view"); - m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Brush size"); m_desc["tool_type"] = _L("Tool type"); m_desc["enforce"] = _L("Enforce seam"); @@ -64,8 +61,7 @@ bool GLGizmoSeam::on_init() {_L("Left mouse button"), m_desc["enforce"]}, {_L("Right mouse button"), m_desc["block"]}, {shift + _L("Left mouse button"), m_desc["remove"]}, - {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, - {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]} + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]} }; return true; @@ -155,20 +151,17 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float space_size = m_imgui->get_style_scaling() * 8; - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x + ImGui::GetStyle().FramePadding.x * 2) - + m_imgui->scaled(1.5f); const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float empty_button_width = m_imgui->calc_button_size("").x; float caption_max = 0.f; float total_text_max = 0.f; - for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) { + for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size"}) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); } - const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left); + const float sliders_left_width = cursor_size_slider_left; const float slider_icon_width = m_imgui->get_slider_icon_size().x; const float sliders_width = m_imgui->scaled(7.0f); @@ -242,28 +235,6 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + sliders_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } - ImGui::Separator(); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 18308b81b2..9d684cdb88 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -380,22 +380,19 @@ std::vector ObjectClipper::point_per_contour() const } -void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal) +void ObjectClipper::set_position_by_ratio(double pos, const Vec3d& normal) { const ModelObject* mo = get_pool()->selection_info()->model_object(); int active_inst = get_pool()->selection_info()->get_active_instance(); double z_shift = get_pool()->selection_info()->get_sla_shift(); - - Vec3d normal; - if(vertical_normal) { - normal = {0, 0, 1}; - }else { - //Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward(); - //if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON) - // camera_dir(2) = 0; - - normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward(); + if (pos == 0. || mo == nullptr || active_inst < 0) { + // No plane, so the raycasts are not clipped. + m_clp_ratio = 0.; + m_clp.reset(); + get_pool()->get_canvas()->set_as_dirty(); + return; } + Vec3d center; if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) { @@ -410,9 +407,6 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool ver float dist = normal.dot(center); - if (pos < 0.) - pos = m_clp_ratio; - m_clp_ratio = pos; m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2 * m_active_inst_bb_radius))); @@ -445,171 +439,5 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou } -using namespace AssembleViewDataObjects; -AssembleViewDataPool::AssembleViewDataPool(GLCanvas3D* canvas) - : m_canvas(canvas) -{ - using c = AssembleViewDataID; - m_data[c::ModelObjectsInfo].reset(new ModelObjectsInfo(this)); - m_data[c::ModelObjectsClipper].reset(new ModelObjectsClipper(this)); -} - -void AssembleViewDataPool::update(AssembleViewDataID required) -{ - assert(check_dependencies(required)); - for (auto& [id, data] : m_data) { - if (int(required) & int(AssembleViewDataID(id))) - data->update(); - else - if (data->is_valid()) - data->release(); - } -} - - -ModelObjectsInfo* AssembleViewDataPool::model_objects_info() const -{ - ModelObjectsInfo* sel_info = dynamic_cast(m_data.at(AssembleViewDataID::ModelObjectsInfo).get()); - assert(sel_info); - return sel_info->is_valid() ? sel_info : nullptr; -} - - -ModelObjectsClipper* AssembleViewDataPool::model_objects_clipper() const -{ - ModelObjectsClipper* oc = dynamic_cast(m_data.at(AssembleViewDataID::ModelObjectsClipper).get()); - // ObjectClipper is used from outside the gizmos to report current clipping plane. - // This function can be called when oc is nullptr. - return (oc && oc->is_valid()) ? oc : nullptr; -} - -#ifndef NDEBUG -// Check the required resources one by one and return true if all -// dependencies are met. -bool AssembleViewDataPool::check_dependencies(AssembleViewDataID required) const -{ - // This should iterate over currently required data. Each of them should - // be asked about its dependencies and it must check that all dependencies - // are also in required and before the current one. - for (auto& [id, data] : m_data) { - // in case we don't use this, the deps are irrelevant - if (!(int(required) & int(AssembleViewDataID(id)))) - continue; - - - AssembleViewDataID deps = data->get_dependencies(); - assert(int(deps) == (int(deps) & int(required))); - } - - -return true; -} -#endif // NDEBUG - - - - -void ModelObjectsInfo::on_update() -{ - if (!get_pool()->get_canvas()->get_model()->objects.empty()) { - m_model_objects = get_pool()->get_canvas()->get_model()->objects; - } - else { - m_model_objects.clear(); - } -} - -void ModelObjectsInfo::on_release() -{ - m_model_objects.clear(); -} - -//int ModelObjectsInfo::get_active_instance() const -//{ -// const Selection& selection = get_pool()->get_canvas()->get_selection(); -// return selection.get_instance_idx(); -//} - - -void ModelObjectsClipper::on_update() -{ - const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects(); - if (model_objects.empty()) - return; - - // which mesh should be cut? - std::vector meshes; - - if (meshes.empty()) - for (auto mo : model_objects) { - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - } - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(mesh->its); - } - m_old_meshes = meshes; - - m_active_inst_bb_radius = get_pool()->get_canvas()->volumes_bounding_box().radius(); - } -} - - -void ModelObjectsClipper::on_release() -{ - m_clippers.clear(); - m_old_meshes.clear(); - m_clp.reset(); - m_clp_ratio = 0.; - -} - -void ModelObjectsClipper::render_cut() const -{ - if (m_clp_ratio == 0.) - return; - const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects(); - - size_t clipper_id = 0; - for (const ModelObject* mo : model_objects) { - Geometry::Transformation assemble_objects_trafo = mo->instances[0]->get_assemble_transformation(); - auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly(); - for (const ModelVolume* mv : mo->volumes) { - Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = assemble_objects_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + vol_trafo.get_offset() * (GLVolume::explosion_ratio - 1.0) + offset_to_assembly * (GLVolume::explosion_ratio - 1.0)); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - // BBS - clipper->render_cut({0.25f, 0.25f, 0.25f, 1.0f}); - - ++clipper_id; - } - } -} - - -void ModelObjectsClipper::set_position(double pos, bool keep_normal) -{ - Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); - const Vec3d& center = get_pool()->get_canvas()->volumes_bounding_box().center(); - float dist = normal.dot(center); - - if (pos < 0.) - pos = m_clp_ratio; - - m_clp_ratio = pos; - m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius * GLVolume::explosion_ratio) - m_clp_ratio * 2 * m_active_inst_bb_radius * GLVolume::explosion_ratio))); - get_pool()->get_canvas()->set_as_dirty(); - -} - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index d2eeda4f64..f89914e445 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -52,7 +52,6 @@ enum class SLAGizmoEventType : unsigned char { class CommonGizmosDataBase; -class AssembleViewDataBase; namespace CommonGizmosDataObjects { class SelectionInfo; class InstancesHider; @@ -62,11 +61,6 @@ namespace CommonGizmosDataObjects { class SupportsClipper; } -namespace AssembleViewDataObjects { - class ModelObjectsInfo; - class ModelObjectsClipper; -} - // Some of the gizmos use the same data that need to be updated ocassionally. // It is also desirable that the data are not recalculated when the gizmos // are just switched, but on the other hand, they should be released when @@ -245,7 +239,7 @@ public: void set_position_to_init_layer(); const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const; void render_cut(const std::vector* ignore_idxs = nullptr) const; - void set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal=false); + void set_position_by_ratio(double pos, const Vec3d& normal); void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); @@ -272,121 +266,6 @@ private: } // namespace CommonGizmosDataObjects -enum class AssembleViewDataID { - None = 0, - ModelObjectsInfo = 1 << 0, - ModelObjectsClipper = 1 << 4, -}; - -class AssembleViewDataPool { -public: - AssembleViewDataPool(GLCanvas3D* canvas); - - // Update all resources and release what is not used. - // Accepts a bitmask of currently required resources. - void update(AssembleViewDataID required); - - // Getters for the data that need to be accessed from the gizmos directly. - AssembleViewDataObjects::ModelObjectsInfo* model_objects_info() const; - AssembleViewDataObjects::ModelObjectsClipper* model_objects_clipper() const; - - GLCanvas3D* get_canvas() const { return m_canvas; } - -private: - std::map> m_data; - GLCanvas3D* m_canvas; - -#ifndef NDEBUG - bool check_dependencies(AssembleViewDataID required) const; -#endif -}; - -// Base class for a wrapper object managing a single resource. -// Each of the enum values above (safe None) will have an object of this kind. -class AssembleViewDataBase { -public: - // Pass a backpointer to the pool, so the individual - // objects can communicate with one another. - explicit AssembleViewDataBase(AssembleViewDataPool* cgdp) - : m_common{ cgdp } {} - virtual ~AssembleViewDataBase() {} - - // Update the resource. - void update() { on_update(); m_is_valid = true; } - - // Release any data that are stored internally. - void release() { on_release(); m_is_valid = false; } - - // Returns whether the resource is currently maintained. - bool is_valid() const { return m_is_valid; } - -#ifndef NDEBUG - // Return a bitmask of all resources that this one relies on. - // The dependent resource must have higher ID than the one - // it depends on. - virtual AssembleViewDataID get_dependencies() const { return AssembleViewDataID::None; } -#endif // NDEBUG - -protected: - virtual void on_release() = 0; - virtual void on_update() = 0; - AssembleViewDataPool* get_pool() const { return m_common; } - - -private: - bool m_is_valid = false; - AssembleViewDataPool* m_common = nullptr; -}; - -namespace AssembleViewDataObjects -{ -class ModelObjectsInfo : public AssembleViewDataBase -{ -public: - explicit ModelObjectsInfo(AssembleViewDataPool* cgdp) - : AssembleViewDataBase(cgdp) {} - - ModelObjectPtrs model_objects() const { return m_model_objects; } - //int get_active_instance() const; - float get_sla_shift() const { return m_z_shift; } - -protected: - void on_update() override; - void on_release() override; - -private: - ModelObjectPtrs m_model_objects; - float m_z_shift = 0.f; -}; - -class ModelObjectsClipper : public AssembleViewDataBase -{ -public: - explicit ModelObjectsClipper(AssembleViewDataPool* cgdp) - : AssembleViewDataBase(cgdp) {} -#ifndef NDEBUG - AssembleViewDataID get_dependencies() const override { return AssembleViewDataID::ModelObjectsInfo; } -#endif // NDEBUG - - void set_position(double pos, bool keep_normal); - double get_position() const { return m_clp_ratio; } - ClippingPlane* get_clipping_plane() const { return m_clp.get(); } - void render_cut() const; - - -protected: - void on_update() override; - void on_release() override; - -private: - std::vector m_old_meshes; - std::vector> m_clippers; - std::unique_ptr m_clp; - double m_clp_ratio = 0.; - double m_active_inst_bb_radius = 0.; -}; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index f9d88b67b8..3eaaf814b6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -278,8 +278,6 @@ bool GLGizmosManager::init() //m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++)); m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); - if(!m_assemble_view_data) - m_assemble_view_data.reset(new AssembleViewDataPool(&m_parent)); for (auto& gizmo : m_gizmos) { if (! gizmo->init()) { @@ -374,7 +372,17 @@ bool GLGizmosManager::init_icon_textures() icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_DARK_HOVER, texture_id)); else return false; - + + for (const auto& [icon, name] : std::initializer_list>{ + { IC_CANVAS_SECTION, "canvas_section" }, { IC_CANVAS_SECTION_HOVER, "canvas_section_hover" }, + { IC_CANVAS_SECTION_DARK, "canvas_section_dark" }, { IC_CANVAS_SECTION_DARK_HOVER, "canvas_section_dark_hover" }, + { IC_CANVAS_SECTION_ACTIVE, "canvas_section_active" }, { IC_CANVAS_SECTION_ACTIVE_HOVER, "canvas_section_active_hover" }, + { IC_CANVAS_SECTION_ACTIVE_DARK, "canvas_section_active_dark" }, { IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, "canvas_section_active_dark_hover" } }) { + if (!IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/" + name + ".svg", 72, 72, texture_id)) + return false; + icon_list.insert(std::make_pair((int) icon, texture_id)); + } + return true; } @@ -476,14 +484,17 @@ void GLGizmosManager::set_hover_id(int id) m_gizmos[m_current]->set_hover_id(id); } -void GLGizmosManager::update_assemble_view_data() +void GLGizmosManager::update_section_view() { - if (m_assemble_view_data) { - if (!wxGetApp().plater()->get_assmeble_canvas3D()->get_wxglcanvas()->IsShown()) - m_assemble_view_data->update(AssembleViewDataID(0)); - else - m_assemble_view_data->update(AssembleViewDataID((int)AssembleViewDataID::ModelObjectsInfo | (int)AssembleViewDataID::ModelObjectsClipper)); - } + if (m_current != FdmSupports && m_current != Seam && m_current != MmSegmentation && m_current != FuzzySkin && m_current != BrimEars) + return; + + CommonGizmosDataObjects::ObjectClipper* clipper = m_common_gizmos_data ? m_common_gizmos_data->object_clipper() : nullptr; + if (clipper == nullptr) + return; + + // Brim ears always cut horizontally. + clipper->set_position_by_ratio(m_parent.get_section_view_ratio(), m_current == BrimEars ? Vec3d::UnitZ() : m_parent.get_section_view_normal()); } void GLGizmosManager::update_data() @@ -496,6 +507,7 @@ void GLGizmosManager::update_data() m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); + update_section_view(); if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing); // Orca: hack: Fix issue that flatten gizmo faces not updated after reload from disk @@ -609,11 +621,11 @@ bool GLGizmosManager::is_allow_select_all() { return false; } -ClippingPlane GLGizmosManager::get_clipping_plane() const +std::optional GLGizmosManager::get_clipping_plane() const { - if (! m_common_gizmos_data - || ! m_common_gizmos_data->object_clipper() - || m_common_gizmos_data->object_clipper()->get_position() == 0.) + if (! m_common_gizmos_data || ! m_common_gizmos_data->object_clipper()) + return std::nullopt; + else if (m_common_gizmos_data->object_clipper()->get_position() == 0.) return ClippingPlane::ClipsNothing(); else { const ClippingPlane& clp = *m_common_gizmos_data->object_clipper()->get_clipping_plane(); @@ -621,18 +633,6 @@ ClippingPlane GLGizmosManager::get_clipping_plane() const } } -ClippingPlane GLGizmosManager::get_assemble_view_clipping_plane() const -{ - if (!m_assemble_view_data - || !m_assemble_view_data->model_objects_clipper() - || m_assemble_view_data->model_objects_clipper()->get_position() == 0.) - return ClippingPlane::ClipsNothing(); - else { - const ClippingPlane& clp = *m_assemble_view_data->model_objects_clipper()->get_clipping_plane(); - return ClippingPlane(-clp.get_normal(), clp.get_data()[3]); - } -} - bool GLGizmosManager::wants_reslice_supports_on_undo() const { return false; @@ -665,12 +665,6 @@ void GLGizmosManager::render_painter_gizmo() gizmo->render_painter_gizmo(); } -void GLGizmosManager::render_painter_assemble_view() const -{ - if (m_assemble_view_data && m_assemble_view_data->model_objects_clipper()) - m_assemble_view_data->model_objects_clipper()->render_cut(); -} - // The icon bar, drawn with GL. void GLGizmosManager::render_overlay() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 63207f741b..855e26c102 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -22,6 +22,7 @@ #include #include #include +#include //BBS: GUI refactor: to support top layout #define BBS_TOOLBAR_ON_TOP 1 @@ -181,7 +182,6 @@ private: bool gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event); public: - std::unique_ptr m_assemble_view_data; enum MENU_ICON_NAME { IC_TOOLBAR_RESET = 0, IC_TOOLBAR_RESET_HOVER, @@ -198,6 +198,14 @@ public: IC_CANVAS_ZOOM_HOVER, IC_CANVAS_ZOOM_DARK, IC_CANVAS_ZOOM_DARK_HOVER, + IC_CANVAS_SECTION, + IC_CANVAS_SECTION_HOVER, + IC_CANVAS_SECTION_DARK, + IC_CANVAS_SECTION_DARK_HOVER, + IC_CANVAS_SECTION_ACTIVE, + IC_CANVAS_SECTION_ACTIVE_HOVER, + IC_CANVAS_SECTION_ACTIVE_DARK, + IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, }; explicit GLGizmosManager(GLCanvas3D& parent); @@ -266,7 +274,8 @@ public: /// Should be called when selection changed /// void update_data(); - void update_assemble_view_data(); + // Passes the canvas' section view to the object clipper of the painting and brim ears gizmos. + void update_section_view(); EType get_current_type() const { return m_current; } GLGizmoBase* get_current() const; @@ -297,8 +306,8 @@ public: bool is_paint_gizmo(); bool is_allow_select_all(); - ClippingPlane get_clipping_plane() const; - ClippingPlane get_assemble_view_clipping_plane() const; + // Empty when the open gizmo has no object clipper. + std::optional get_clipping_plane() const; bool wants_reslice_supports_on_undo() const; bool is_in_editing_mode(bool error_notification = false) const; @@ -307,7 +316,6 @@ public: void on_change_color_mode(bool is_dark); void render_current_gizmo() const; void render_painter_gizmo(); - void render_painter_assemble_view() const; void render_overlay(); void render_overlay_input_window(); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index f7c0f23ae8..92051d116e 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -196,13 +196,13 @@ void KBShortcutsDialog::fill_pages() mouse(Section::Camera, _L("Middle mouse"), "middle_mouse_drag_action"), mouse(Section::Camera, _L("Right mouse"), "right_mouse_drag_action"), fixed(Section::Camera, { wheel }, L("Zoom View")), + fixed(Section::Display, { alt, wheel }, L("Move section plane")), }); page(_L("Painting"), _L("Available while a painting gizmo is open: supports, seam, fuzzy skin or color painting."), ShortcutContext::Painting, { fixed(Section::Gizmos, { esc }, L("Deselect All")), fixed(Section::Gizmos, { shift, left_button }, L("Move: press to snap by 1mm")), fixed(Section::PaintingTools, { ctrl, wheel }, L("Support/Color Painting: adjust pen radius")), - fixed(Section::PaintingTools, { alt, wheel }, L("Support/Color Painting: adjust section position")), }); page(_L("Objects list"), _L("Available while the object list has focus."), ShortcutContext::ObjectList, { @@ -216,6 +216,7 @@ void KBShortcutsDialog::fill_pages() page(_L("Preview"), _L("Available while the 3D view on the Preview tab has focus."), ShortcutContext::Preview, { fixed(Section::Sliders, { shift_ctrl, any_key }, L("Move slider 5x faster")), fixed(Section::Sliders, { shift_ctrl, wheel }, L("Scroll slider 5x faster")), + fixed(Section::Display, { alt, wheel }, L("Move section plane")), }); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 142e4e2482..89616345eb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -7891,6 +7891,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D = new View3D(panel_3d, bed, &model, config, &background_process); //BBS: use partplater's gcode preview = new Preview(panel_3d, bed, &model, config, &background_process, partplate_list.get_current_slice_result(), [this]() { schedule_background_process(); }); + preview->get_canvas3d()->share_section_view(*view3D->get_canvas3d()); assemble_view = new AssembleView(panel_3d, bed, &model, config, &background_process);