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