Add section view feature for 3D canvas (#15879)

This commit is contained in:
Ian Bassi
2026-10-02 11:50:43 -03:00
committed by GitHub
parent 6c6e8be43d
commit a1ad2b4425
31 changed files with 599 additions and 612 deletions
+103
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#fafafa;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#009688;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#00675b;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#008172;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#26a69a;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#393c42;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#283232;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#e5f0ee;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+4
View File
@@ -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<float, 4>& plane);
//
// ************************************************************************
+61 -4
View File
@@ -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";
+67 -4
View File
@@ -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";
+5
View File
@@ -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<float, 4>& plane)
{
m_impl->set_clipping_plane(plane);
}
EViewType Viewer::get_view_type() const
{
return m_impl->get_view_type();
+6
View File
@@ -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));
+7
View File
@@ -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<float, 4>& 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<float, 4> m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f };
void apply_pending_updates();
void update_view_full_range();
void update_color_ranges();
+9
View File
@@ -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<float>();
m_viewer.set_clipping_plane(plane.is_active() ?
std::array<float, 4>{ normal.x(), normal.y(), normal.z(), float(plane.get_offset()) } :
std::array<float, 4>{ 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())
+2
View File
@@ -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);
+231 -68
View File
@@ -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<const GLVolume*, MeshClipper> 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<std::pair<wxString, wxString>> 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<ClippingPlane> 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();
}
+28
View File
@@ -619,6 +619,19 @@ private:
std::array<ClippingPlane, 2> 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<SectionView> m_section_view{ std::make_shared<SectionView>(this) };
std::map<const GLVolume*, MeshClipper> m_section_view_caps;
// Its release would open the hidden window menu on Windows.
bool m_alt_wheel_used{ false };
std::array<SlaCap, 2> 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<ColorRGBA, 2>& 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;
+2 -40
View File
@@ -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<wxString> 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<wxString> 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();
+4 -35
View File
@@ -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<wxString, wxString> enforce_shortcut = {_L("Left mouse button"), _L("Enforce supports")};
std::pair<wxString, wxString> block_shortcut = {_L("Right mouse button"), _L("Block supports")};
std::pair<wxString, wxString> remove_shortcut = {shift + _L("Left mouse button"), _L("Erase")};
std::pair<wxString, wxString> 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<std::string, 5>{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 4>{"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);
+6 -41
View File
@@ -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<wxString, wxString> add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]};
std::pair<wxString, wxString> remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]};
std::pair<wxString, wxString> 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);
@@ -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<wxString, wxString> paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
std::pair<wxString, wxString> erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
std::pair<wxString, wxString> 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<std::string, 6>{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 5>{"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);
@@ -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
+3 -32
View File
@@ -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<std::string, 6>{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 4>{"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);
+8 -180
View File
@@ -380,22 +380,19 @@ std::vector<Vec3d> 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<ModelObjectsInfo*>(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<ModelObjectsClipper*>(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<const TriangleMesh*> 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
+1 -122
View File
@@ -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<size_t>* 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<AssembleViewDataID, std::unique_ptr<AssembleViewDataBase>> 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<const TriangleMesh*> m_old_meshes;
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
std::unique_ptr<ClippingPlane> m_clp;
double m_clp_ratio = 0.;
double m_active_inst_bb_radius = 0.;
};
}
} // namespace GUI
} // namespace Slic3r
+26 -32
View File
@@ -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<std::pair<MENU_ICON_NAME, const char*>>{
{ 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<ClippingPlane> 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()
{
+13 -5
View File
@@ -22,6 +22,7 @@
#include <cereal/specialize.hpp>
#include <wx/timer.h>
#include <map>
#include <optional>
//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<AssembleViewDataPool> 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
/// </summary>
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<ClippingPlane> 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();
+2 -1
View File
@@ -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")),
});
}
+1
View File
@@ -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);