diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml
index 8c7674f4d7..98f0d66032 100644
--- a/.github/workflows/build_orca.yml
+++ b/.github/workflows/build_orca.yml
@@ -259,6 +259,7 @@ jobs:
# Thanks to RaySajuuk, it's working now
- name: Sign app and notary
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
+ timeout-minutes: 30
working-directory: ${{ github.workspace }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 821d7e40c2..c431bff558 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -823,7 +823,9 @@ if(SLIC3R_STATIC)
set(TBB_STATIC 1)
endif()
set(TBB_DEBUG 1)
-set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "")
+if ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" OR MSVC)
+ set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "")
+endif()
find_package(TBB REQUIRED)
# include_directories(SYSTEM ${TBB_INCLUDE_DIRS})
# add_definitions(${TBB_DEFINITIONS})
diff --git a/deps_src/qhull/CMakeLists.txt b/deps_src/qhull/CMakeLists.txt
index 9cead64e2c..82a7bf627e 100644
--- a/deps_src/qhull/CMakeLists.txt
+++ b/deps_src/qhull/CMakeLists.txt
@@ -19,11 +19,7 @@ if(Qhull_FOUND)
message(STATUS "Using qhull from system.")
if(SLIC3R_STATIC)
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhullstatic_r" RelWithDebInfo Release)
- if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
- target_link_libraries(qhull INTERFACE Qhull::qhullcpp_d Qhull::qhullstatic_rd)
- else()
- target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
- endif()
+ target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
else()
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release)
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r)
diff --git a/docs/HLSD/scene-benchmark.md b/docs/HLSD/scene-benchmark.md
new file mode 100644
index 0000000000..21e4ec574c
--- /dev/null
+++ b/docs/HLSD/scene-benchmark.md
@@ -0,0 +1,93 @@
+# 3D Scene Benchmark: High Level Design
+
+## Why it exists
+
+Rendering changes, such as the realistic view, shadows or SSAO, need a number to compare
+before and after, and user reports of a slow viewport need a way to say how slow. The FPS
+overlay and the render timings overlay show live values while someone drags the camera,
+which varies from run to run with the model, the path of the mouse and the view.
+
+The benchmark renders a fixed model along a fixed camera path in both 3D views, so two
+runs on the same machine differ only by the code or the settings, and prints a report that
+can be pasted into an issue.
+
+## What it does
+
+`run_scene_benchmark()` in `src/slic3r/GUI/SceneBenchmark.cpp` is reached from Help >
+Benchmark 3D Scene, the command palette and Preferences > Graphics. After a confirmation
+it starts a new project, which asks to save the current one if needed, loads the
+OrcaSliced Combo handy model and arranges it. A small dialog in a corner of the 3D view
+then shows the progress; every other window is disabled until the run ends, so a click
+cannot change the scene being measured. Cancel or Esc stops the run.
+
+The run goes through these stages, driven by a timer while it waits and by idle events
+while it renders:
+
+1. Loading: waits until the UI job worker is idle, so the arrange job has moved the
+ objects. The orbit target is the center of the objects on the current plate, and the
+ base zoom fits their bounding box in the viewport.
+2. Prepare: renders the scene in the Prepare view.
+3. Slicing: slices the plate and switches to Preview, then waits for the G-code preview
+ to load. If slicing fails, the report holds Prepare alone.
+4. Preview: renders the scene in the Preview view, with the slicing progress notification
+ hidden.
+5. Layers: renders the Preview view again while the layer slider moves, which is what
+ makes dragging it feel slow on large prints.
+
+The dialog then shows the report, with a button to copy it. A scene cut short, because its
+view was hidden, is left out of the report.
+
+## Rendering a scene
+
+Each scene renders 30 warm-up frames, then the camera path twice, 360 frames each time.
+
+- The first pass times the frames. A frame's time is the interval between the starts of
+ consecutive benchmark frames, so it includes the event loop between them.
+- The second pass averages the render timings. The frame profiler flushes the GL command
+ queue after each section, which slows a frame down, so it only runs in this pass.
+ `FrameProfiler::start_averaging()` flags every frame begun afterwards, and
+ `finish_averaging()` waits for the flagged frames still on the GPU and returns the mean
+ CPU and GPU time of each section per profiled frame.
+- A section's GPU time is taken between a timestamp before its commands and one after
+ them. The first is only sent along with those commands, so when the GPU finishes a
+ section before the CPU has issued the next one, the wait counts in neither.
+
+The dialog renders one frame per idle event by calling `GLCanvas3D::render()`, which
+redraws the whole scene. While `GLCanvas3D::set_benchmarking()` is on, the canvas does not
+render from its own idle handler, so no other frame is drawn in between, and it skips the
+picking pass and the FPS and render timings overlays, which depend on the mouse and on
+preferences. The FPS cap does not apply, since it only paces idle redraws.
+
+VSync is turned off for the scene through `wxGLCanvas::SetSwapInterval(0)`, so the frame
+rate is what the GPU and CPU can reach rather than the display's refresh rate, and the
+previous interval is restored afterwards. When the platform cannot report the current
+interval (EGL), it is left as it is and the report says so.
+
+The camera path makes two turns around the target while the view rises three times from
+25 degrees below the plate to 85 degrees above it and the zoom goes twice between 0.6 and
+1.4 times the base zoom. The camera stays at the default distance, so the perspective is
+the same in every run. The camera the scene started with is restored at its end.
+
+The Layers scene holds the camera at the start of that path and moves the top of the layer
+slider instead, from the last layer down to the first and back up in each pass. It goes
+through `IMSlider::SetHigherValue()`, as a drag does, so every frame applies a new layer
+range to the toolpaths and the objects before drawing them, including a new shadow map when
+the shadows are static. Its warm-up frames lead into the start of the path, so the slider
+moves in every frame. The slider position it started from is restored at its end.
+
+## The report
+
+The report is plain English text, so it reads the same in every language:
+
+- The version and build commit, the GPU and OpenGL version, the viewport size and camera
+ type, and the graphics settings that change the cost of a frame: MSAA samples as read
+ from the framebuffer, FXAA, the scene cache, VSync and the realistic view options.
+- The printer and process presets the model was sliced with, marked when they have
+ unsaved changes, and the toolpath vertices and layers they produced, since the Preview
+ scenes cost more with more toolpaths.
+- For each scene, the average FPS and the average, median, 95th percentile, 99th
+ percentile and maximum frame time. Percentiles are nearest-rank, so each is a measured
+ frame (`frame_time_stats()`).
+- For each scene, the render timings table: the CPU and GPU milliseconds of each section
+ of a frame, and their total. Without timer queries (OpenGL 3.3 or `ARB_timer_query`) the
+ table says that the driver does not support them.
diff --git a/docs/HLSD/section-view.md b/docs/HLSD/section-view.md
new file mode 100644
index 0000000000..1cc1fd726e
--- /dev/null
+++ b/docs/HLSD/section-view.md
@@ -0,0 +1,105 @@
+# 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 draw with a program of their own, which takes the
+ plane and discards the fragments on the clipped side, so what is cut away casts no shadow either.
+ Their cut faces are not drawn, since the part left behind casts the shadow of its own section.
+ Preview shells are drawn by another shader and are not clipped.
+- **Picking.** `get_raycaster_clipping_plane()` returns the same plane, so hover, selection and
+ the perspective pan anchor ignore what the user cannot see.
+
+## Gizmos
+
+A gizmo that clips its object itself owns the gizmo data pool's `ObjectClipper`, and its plane
+replaces the canvas section while the gizmo is open. `GLGizmosManager::get_clipping_plane()`
+reports that plane, or nothing when no open gizmo has a clipper. There are two cases.
+
+- **Painting tools and brim ears** show the canvas section on the object they edit.
+ `GLGizmosManager::update_section_view()` copies the ratio and normal into their clipper
+ whenever the pool is updated or the section changes. The clipper then places the plane across
+ the edited instance, which is the only object shown. The painting tools keep clipping their
+ own triangles, raycasts and cut face through it. Brim ears always cut horizontally from the
+ top, because the ears sit on the plate. At ratio 0 the clipper holds no plane at all, so the
+ raycasts are not clipped.
+- **Cut and mesh boolean** use the clipper for their own purposes, so the canvas section is
+ suspended while they are open.
+
+Every other gizmo, including move, rotate and scale, leaves the canvas section in place.
+
+## Alt + mouse wheel
+
+The canvas handles Alt + wheel after the gizmos had their turn, so it works the same in every
+tab and with any gizmo open. On Windows, releasing Alt when no key was pressed since it went down
+opens the window menu, and a wheel turn does not count as a key. Under the custom title bar that
+menu is invisible, yet it takes the keyboard and the next click, which looks like a frozen 3D
+view. After Alt + wheel the canvas therefore consumes the Alt release instead of passing it on.
+
+## Redraw
+
+The button and the panel are part of the ImGui overlay, which is built after the frame's scene is
+drawn. A change to the section therefore marks the scene dirty and asks for one more frame. The
+cached scene is never reused across a change.
diff --git a/localization/i18n/list.txt b/localization/i18n/list.txt
index 4488a91500..6da0507495 100644
--- a/localization/i18n/list.txt
+++ b/localization/i18n/list.txt
@@ -181,6 +181,7 @@ src/slic3r/GUI/PrivacyUpdateDialog.cpp
src/slic3r/GUI/PublishDialog.cpp
src/slic3r/GUI/PublishSettingsDialog.cpp
src/slic3r/GUI/SavePresetDialog.cpp
+src/slic3r/GUI/SceneBenchmark.cpp
src/slic3r/GUI/Search.cpp
src/slic3r/GUI/SettingsIndex.cpp
src/slic3r/GUI/SpeedDialDialog.cpp
diff --git a/resources/images/canvas_section.svg b/resources/images/canvas_section.svg
new file mode 100644
index 0000000000..553c01b22c
--- /dev/null
+++ b/resources/images/canvas_section.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_active.svg b/resources/images/canvas_section_active.svg
new file mode 100644
index 0000000000..1d8351730b
--- /dev/null
+++ b/resources/images/canvas_section_active.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_active_dark.svg b/resources/images/canvas_section_active_dark.svg
new file mode 100644
index 0000000000..45cc5a09c6
--- /dev/null
+++ b/resources/images/canvas_section_active_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_active_dark_hover.svg b/resources/images/canvas_section_active_dark_hover.svg
new file mode 100644
index 0000000000..9472dd39cd
--- /dev/null
+++ b/resources/images/canvas_section_active_dark_hover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_active_hover.svg b/resources/images/canvas_section_active_hover.svg
new file mode 100644
index 0000000000..8664ba71d9
--- /dev/null
+++ b/resources/images/canvas_section_active_hover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_dark.svg b/resources/images/canvas_section_dark.svg
new file mode 100644
index 0000000000..3f5660f889
--- /dev/null
+++ b/resources/images/canvas_section_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_dark_hover.svg b/resources/images/canvas_section_dark_hover.svg
new file mode 100644
index 0000000000..b0d38d38e5
--- /dev/null
+++ b/resources/images/canvas_section_dark_hover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/canvas_section_hover.svg b/resources/images/canvas_section_hover.svg
new file mode 100644
index 0000000000..6bd98eda77
--- /dev/null
+++ b/resources/images/canvas_section_hover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/shaders/110/gouraud.fs b/resources/shaders/110/gouraud.fs
index 59c4e4d410..c5efeef277 100644
--- a/resources/shaders/110/gouraud.fs
+++ b/resources/shaders/110/gouraud.fs
@@ -63,6 +63,10 @@ uniform float shadow_map_texel;
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
varying vec3 clipping_planes_dots;
varying float color_clip_plane_dot;
@@ -155,7 +159,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
- float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
+ float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
diff --git a/resources/shaders/110/gouraud.vs b/resources/shaders/110/gouraud.vs
index 184529be82..37be529b09 100644
--- a/resources/shaders/110/gouraud.vs
+++ b/resources/shaders/110/gouraud.vs
@@ -39,6 +39,10 @@ uniform vec2 z_range;
uniform vec4 clipping_plane;
// Color clip plane - general orientation. Used by the cut gizmo.
uniform vec4 color_clip_plane;
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
attribute vec3 v_position;
attribute vec3 v_normal;
@@ -60,11 +64,11 @@ void main()
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
- float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
+ float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 position = view_model_matrix * vec4(v_position, 1.0);
- intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
diff --git a/resources/shaders/110/phong.fs b/resources/shaders/110/phong.fs
index 5fedf7edff..7b9a522eaa 100644
--- a/resources/shaders/110/phong.fs
+++ b/resources/shaders/110/phong.fs
@@ -70,6 +70,10 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
varying vec3 clipping_planes_dots;
varying float color_clip_plane_dot;
@@ -193,7 +197,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
- float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
+ float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
@@ -248,9 +252,9 @@ void main()
vec3 normal = normalize(eye_normal);
vec3 view_dir = normalize(-eye_position);
- float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+ float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
- vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
+ vec3 half_top = normalize(top_light_dir() + view_dir);
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
diff --git a/resources/shaders/110/printbed_shadow.fs b/resources/shaders/110/printbed_shadow.fs
index a4fd6801dd..394cfac5ee 100644
--- a/resources/shaders/110/printbed_shadow.fs
+++ b/resources/shaders/110/printbed_shadow.fs
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
+// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
+uniform vec4 shadow_bounds;
varying vec4 world_pos;
@@ -33,6 +35,8 @@ float shadow_occlusion()
void main()
{
+ if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
+ discard;
float occ = shadow_occlusion();
if (occ <= 0.0)
discard;
diff --git a/resources/shaders/140/gouraud.fs b/resources/shaders/140/gouraud.fs
index e099984b46..7b315f90c0 100644
--- a/resources/shaders/140/gouraud.fs
+++ b/resources/shaders/140/gouraud.fs
@@ -72,6 +72,10 @@ uniform float shadow_map_texel;
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
in vec3 clipping_planes_dots;
in float color_clip_plane_dot;
@@ -206,7 +210,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
- float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
+ float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
diff --git a/resources/shaders/140/gouraud.vs b/resources/shaders/140/gouraud.vs
index a31e751964..11fc4b70c8 100644
--- a/resources/shaders/140/gouraud.vs
+++ b/resources/shaders/140/gouraud.vs
@@ -39,6 +39,10 @@ uniform vec2 z_range;
uniform vec4 clipping_plane;
// Color clip plane - general orientation. Used by the cut gizmo.
uniform vec4 color_clip_plane;
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
in vec3 v_position;
in vec3 v_normal;
@@ -60,11 +64,11 @@ void main()
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
- float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
+ float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 position = view_model_matrix * vec4(v_position, 1.0);
- intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
diff --git a/resources/shaders/140/phong.fs b/resources/shaders/140/phong.fs
index f847763658..03ffbb3e5c 100644
--- a/resources/shaders/140/phong.fs
+++ b/resources/shaders/140/phong.fs
@@ -80,6 +80,10 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
+// ORCA: realistic view - static shadows also light the scene from their fixed light.
+uniform bool use_static_light;
+uniform vec3 static_light_dir;
+vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
in vec3 clipping_planes_dots;
in float color_clip_plane_dot;
@@ -250,7 +254,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
- float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
+ float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
@@ -305,9 +309,9 @@ void main()
vec3 normal = normalize(eye_normal);
vec3 view_dir = normalize(-eye_position);
- float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+ float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
- vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
+ vec3 half_top = normalize(top_light_dir() + view_dir);
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
diff --git a/resources/shaders/140/printbed_shadow.fs b/resources/shaders/140/printbed_shadow.fs
index 650d88950e..8804806d64 100644
--- a/resources/shaders/140/printbed_shadow.fs
+++ b/resources/shaders/140/printbed_shadow.fs
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
+// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
+uniform vec4 shadow_bounds;
in vec4 world_pos;
@@ -35,6 +37,8 @@ float shadow_occlusion()
void main()
{
+ if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
+ discard;
float occ = shadow_occlusion();
if (occ <= 0.0)
discard;
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index d639604f51..061665fbe6 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -304,6 +304,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
+ if (get(SETTING_OPENGL_SHOW_RENDER_TIMINGS).empty())
+ set_bool(SETTING_OPENGL_SHOW_RENDER_TIMINGS, false);
+
if (get(SETTING_OPENGL_REALISTIC_MODE).empty())
set_bool(SETTING_OPENGL_REALISTIC_MODE, false);
@@ -316,8 +319,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
- if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty())
- set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false);
+ // Replaces the on/off setting, whose shadows turned with the camera.
+ if (get(SETTING_OPENGL_REALISTIC_SHADOWS).empty())
+ set(SETTING_OPENGL_REALISTIC_SHADOWS, get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) ? "orbit" : "off");
if (get(SETTING_OPENGL_PHONG_SMOOTH_NORMALS).empty())
set_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS, false);
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index 1bf6e730d0..1486cb6f9f 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -37,10 +37,13 @@ using namespace nlohmann;
#define SETTING_OPENGL_SCENE_CACHE "opengl_scene_cache"
#define SETTING_OPENGL_SKIP_IDENTICAL_FRAMES "opengl_skip_identical_frames"
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
+#define SETTING_OPENGL_SHOW_RENDER_TIMINGS "opengl_show_render_timings"
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model"
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
+// off, static (light fixed in the world) or orbit (light turning with the camera)
+#define SETTING_OPENGL_REALISTIC_SHADOWS "opengl_realistic_shadows"
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview"
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 8658fc2d66..88a9396dd0 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -8,6 +8,7 @@
#include "format.hpp"
#include "GCode/Thumbnails.hpp"
+#include
#include
#include
#include
@@ -690,19 +691,46 @@ std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolume
return variant_string;
}
+int find_variant_index(const std::string& variant, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based)
+{
+ const int count = int(variant_list.empty() ? variant_ids_1based.size() : variant_list.size());
+ if (count == 0)
+ return 0;
+ auto same_id = [&](int index) {
+ return variant_id_1based < 0 || variant_ids_1based.empty() || (index < int(variant_ids_1based.size()) && variant_ids_1based[index] == variant_id_1based);
+ };
+ for (int index = 0; index < int(variant_list.size()); ++index)
+ if (variant_list[index] == variant && same_id(index))
+ return index;
+ // Without this variant, use the id's own first variant (usually Standard), not variant index 0,
+ // which belongs to the first filament or extruder.
+ for (int index = 0; index < count; ++index)
+ if (same_id(index))
+ return index;
+ return -1;
+}
+
+std::vector map_variant_indices(const std::vector& variants, const std::vector& ids,
+ const std::vector& from_variants, const std::vector& from_ids)
+{
+ const size_t count = variants.empty() ? ids.size() : variants.size();
+ std::vector variant_index(count);
+ for (size_t index = 0; index < count; ++index) {
+ if (!ids.empty() && index >= ids.size()) {
+ variant_index[index] = -1;
+ continue;
+ }
+ variant_index[index] = find_variant_index(index < variants.size() ? variants[index] : std::string(),
+ ids.empty() ? -1 : ids[index], from_variants, from_ids);
+ }
+ return variant_index;
+}
+
int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based)
{
assert(variant_list.size() == variant_ids_1based.size());
- std::string extruder_variant = get_extruder_variant_string(extruder_type, volume_type);
- for (int index = 0; index < int(variant_list.size()); ++index) {
- if (extruder_variant == variant_list[index] && variant_ids_1based[index] == variant_id_1based) { return index; }
- }
- // Without this variant, use the id's own first variant (usually Standard), not variant index 0,
- // which belongs to the first filament or extruder.
- for (int index = 0; index < int(variant_list.size()); ++index) {
- if (variant_ids_1based[index] == variant_id_1based) { return index; }
- }
- return 0;
+ const int index = find_variant_index(get_extruder_variant_string(extruder_type, volume_type), variant_id_1based, variant_list, variant_ids_1based);
+ return std::max(index, 0);
}
std::set get_extruder_supported_nozzle_volume_types(const DynamicPrintConfig &printer_config, int extruder_id)
@@ -10911,14 +10939,23 @@ void normalize_filament_values_to_variants(DynamicPrintConfig &config)
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
+ // The values are one per filament, without variant strings, or a single value for all of them. The
+ // variant strings do not change today's mapping; they are passed so a rule that reads them applies here too.
+ const auto *variants = config.option("filament_extruder_variant");
+ const std::vector variant_list = variants && variants->size() == self_index->size() ? variants->values : std::vector();
+ std::vector filament_ids(filament_count);
+ std::iota(filament_ids.begin(), filament_ids.end(), 1);
+ const std::vector from_filaments = map_variant_indices(variant_list, self_index->values, {}, filament_ids);
+ const std::vector from_single = map_variant_indices(variant_list, self_index->values, {}, {});
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
- std::unique_ptr per_filament(opt->clone());
- // set_at() takes the first value for a filament past the end of a single-value vector
+ const std::vector &variant_index = opt->size() == size_t(filament_count) ? from_filaments : from_single;
+ std::unique_ptr source(opt->clone());
+ // -1 and a single-value source both resolve to the first value through get_at()
for (size_t variant = 0; variant < self_index->size(); ++variant)
- opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
+ opt->set_at(source.get(), variant, variant_index[variant]);
}
}
@@ -11752,8 +11789,14 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
else
variant_index.resize(1, 0);
+ // A parent variant the child does not list (the parent gained it after the child was saved, or the
+ // child lists none) takes the child's first variant of the same extruder, as slicing does.
+ // Left unmatched, the parent's value would silently replace the user's.
if (target_variant_count == 0) {
- variant_index[0] = 0;
+ // The child's one value belongs to the extruder of the parent's first variant.
+ if (cur_variant_count > 0)
+ variant_index = map_variant_indices(cur_extruder_variants, cur_extruder_ids, {},
+ cur_extruder_ids.empty() ? std::vector() : std::vector{cur_extruder_ids[0]});
}
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
//should not happen
@@ -11765,19 +11808,8 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" size of %1% = %2%, not equal to size of %3% = %4%")
%extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size();
}
- else {
- for (int i = 0; i < cur_variant_count; i++)
- {
- for (int j = 0; j < target_variant_count; j++)
- {
- if ((cur_extruder_variants[i] == target_extruder_variants[j])
- &&(cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j])))
- {
- variant_index[i] = j;
- break;
- }
- }
- }
+ else if (cur_variant_count > 0) {
+ variant_index = map_variant_indices(cur_extruder_variants, cur_extruder_ids, target_extruder_variants, target_extruder_ids);
}
const t_config_option_keys &keys = new_config.keys();
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index b41fc3a0d9..b8fba675ae 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -576,8 +576,19 @@ enum PrimeVolumeMode {
extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type);
-// Base slot lookup: scans a variant list (paired with its 1-based extruder/filament ids) for the
-// entry matching the given extruder/volume type and id. Returns 0 when no entry matches.
+// The variant index a value is taken from: in a variant list paired with its 1-based extruder or
+// filament ids, the variant with the same variant string and id, else that id's first variant, else -1.
+// variant_id_1based < 0 or empty variant_ids_1based match any id. A list without variant strings has
+// one variant per id, and one with neither variant strings nor ids has a single variant.
+extern int find_variant_index(const std::string& variant, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based);
+// find_variant_index for every variant of a list paired with its ids, into from_variants/from_ids.
+// A variant past the end of a shorter id list has no id and gets -1.
+extern std::vector map_variant_indices(const std::vector& variants, const std::vector& ids,
+ const std::vector& from_variants, const std::vector& from_ids);
+
+// Variant index lookup: scans a variant list (paired with its 1-based extruder/filament ids) for the
+// entry matching the given extruder/volume type and id, as find_variant_index. Returns 0 when the id
+// has no variant.
extern int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based);
static std::set get_valid_nozzle_volume_type() {
diff --git a/src/libvgcode/include/Types.hpp b/src/libvgcode/include/Types.hpp
index 2187238ef0..099f622dc3 100644
--- a/src/libvgcode/include/Types.hpp
+++ b/src/libvgcode/include/Types.hpp
@@ -37,6 +37,8 @@ static constexpr float MAX_WIPES_RADIUS_MM = 1.0f;
// Used for positions, displacements and so on.
//
using Vec3 = std::array;
+// ORCA: the per vertex data uploaded as GL_RGBA32F, see extract_pos_and_or_hwa().
+using Vec4 = std::array;
//
// 4x4 square matrix with elements in column-major order:
diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp
index 179105032d..458bc4b69f 100644
--- a/src/libvgcode/include/Viewer.hpp
+++ b/src/libvgcode/include/Viewer.hpp
@@ -76,6 +76,14 @@ public:
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
+ //
+ // ORCA: section view. Cuts away where dot(plane, (x, y, z, 1)) < 0; { 0, 0, 0, 1 } keeps all.
+ //
+ void set_clipping_plane(const std::array& plane);
+ //
+ // ORCA: direction to the top light in eye space, shading the toolpaths and biasing their shadow lookup.
+ //
+ void set_light_top_dir(const Vec3& direction);
//
// ************************************************************************
diff --git a/src/libvgcode/src/Shaders.hpp b/src/libvgcode/src/Shaders.hpp
index 9f546d6dc8..eaf143f583 100644
--- a/src/libvgcode/src/Shaders.hpp
+++ b/src/libvgcode/src/Shaders.hpp
@@ -14,7 +14,7 @@ static const char* Segments_Vertex_Shader =
"#version 150\n"
"#define POINTY_CAPS\n"
"#define FIX_TWISTING\n"
-"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
+"uniform vec3 light_top_dir;\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
@@ -31,13 +31,11 @@ static const char* Segments_Vertex_Shader =
"uniform samplerBuffer height_width_angle_tex;\n"
"uniform samplerBuffer color_tex;\n"
"uniform usamplerBuffer segment_index_tex;\n"
-// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
-// world_position, so the caster would write a depth the receiver never looks up.
-"uniform float bias_scale;\n"
-// draw the instances last to first, top layers before the ones they hide, so that early depth
-// 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: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
+"uniform bool reverse_order;\n"
+"uniform int instances_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,13 +46,25 @@ 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"
-"vec3 decode_color(float color) {\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"
+// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
+"uniform ivec2 lit_layers;\n"
+"uniform int grey_below_layer;\n"
+"uniform float dim_brightness;\n"
+"uniform int kept_vertex;\n"
+"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
-" int r = (c >> 16) & 0xFF;\n"
-" int g = (c >> 8) & 0xFF;\n"
-" int b = (c >> 0) & 0xFF;\n"
+" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
+" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
+" if (dim_brightness >= 0.0)\n"
+" rgb = floor(rgb * dim_brightness);\n"
+" else if (layer < grey_below_layer)\n"
+" rgb = vec3(64.0);\n"
+" }\n"
" float f = 1.0 / 255.0f;\n"
-" return f * vec3(r, g, b);\n"
+" return f * rgb;\n"
"}\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -63,7 +73,7 @@ static const char* Segments_Vertex_Shader =
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"void main() {\n"
-" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
+" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
" int id_b = id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
@@ -152,22 +162,73 @@ static const char* Segments_Vertex_Shader =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
-" eye_position.z += bias * bias_scale;\n"
+" eye_position.z += bias;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
-" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
+" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
" color = color_base * (ambient + emission);\n"
" 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";
+// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
+static const char* Segments_Shadow_Caster_Vertex_Shader =
+"#version 150\n"
+"const vec3 UP = vec3(0, 0, 1);\n"
+"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
+"uniform mat4 view_matrix;\n"
+"uniform mat4 projection_matrix;\n"
+"uniform samplerBuffer position_tex;\n"
+"uniform samplerBuffer height_width_angle_tex;\n"
+"uniform usamplerBuffer segment_index_tex;\n"
+"uniform bool reverse_order;\n"
+"uniform int instances_count;\n"
+"out vec3 world_position;\n"
+"void main() {\n"
+" int segment = gl_VertexID / 6;\n"
+" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
+" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
+" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
+" int id = corner < 2 ? id_a : id_a + 1;\n"
+" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
+" vec3 pos_b = texelFetch(position_tex, id_a + 1).xyz;\n"
+" vec3 line = pos_b - pos_a;\n"
+" float line_len = length(line);\n"
+" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
+" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
+" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
+" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
+" vec3 side_dir = cross(to_light, line_dir);\n"
+" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
+" vec2 height_width = texelFetch(height_width_angle_tex, id).xy;\n"
+" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
+" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
+" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
+" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
+" world_position = pos;\n"
+" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
+"}\n";
+
+// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
+static const char* Segments_Shadow_Caster_Fragment_Shader =
+"#version 150\n"
+"uniform vec4 clipping_plane;\n"
+"in vec3 world_position;\n"
+"void main() {\n"
+" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
+" discard;\n"
+"}\n";
+
static const char* Segments_Fragment_Shader =
"#version 150\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
-"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
+"uniform vec3 light_top_dir;\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
@@ -178,23 +239,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), light_top_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 +276,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,16 +336,28 @@ 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"
-"vec3 decode_color(float color) {\n"
+"out float clipping_plane_dot;\n"
+// ORCA: as in Segments_Vertex_Shader.
+"uniform ivec2 lit_layers;\n"
+"uniform int grey_below_layer;\n"
+"uniform float dim_brightness;\n"
+"uniform int kept_vertex;\n"
+"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
-" int r = (c >> 16) & 0xFF;\n"
-" int g = (c >> 8) & 0xFF;\n"
-" int b = (c >> 0) & 0xFF;\n"
+" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
+" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
+" if (dim_brightness >= 0.0)\n"
+" rgb = floor(rgb * dim_brightness);\n"
+" else if (layer < grey_below_layer)\n"
+" rgb = vec3(64.0);\n"
+" }\n"
" float f = 1.0 / 255.0f;\n"
-" return f * vec3(r, g, b);\n"
+" return f * rgb;\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -263,11 +377,13 @@ 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"
" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n"
-" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
+" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
@@ -275,8 +391,11 @@ static const char* Options_Vertex_Shader =
static const char* Options_Fragment_Shader =
"#version 150\n"
"in vec3 color;\n"
+"in float clipping_plane_dot;\n"
"out vec4 fragment_color;\n"
"void main() {\n"
+" if (clipping_plane_dot < 0.0)\n"
+" discard;\n"
" fragment_color = vec4(color, 1.0);\n"
"}\n";
diff --git a/src/libvgcode/src/ShadersES.hpp b/src/libvgcode/src/ShadersES.hpp
index 4194b836e3..12871f6249 100644
--- a/src/libvgcode/src/ShadersES.hpp
+++ b/src/libvgcode/src/ShadersES.hpp
@@ -15,7 +15,7 @@ static const char* Segments_Vertex_Shader_ES =
"precision lowp usampler2D;\n"
"#define POINTY_CAPS\n"
"#define FIX_TWISTING\n"
-"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
+"uniform vec3 light_top_dir;\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
@@ -32,6 +32,11 @@ 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: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
+"uniform bool reverse_order;\n"
+"uniform int instances_count;\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,13 +47,25 @@ 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"
-"vec3 decode_color(float color) {\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"
+// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
+"uniform ivec2 lit_layers;\n"
+"uniform int grey_below_layer;\n"
+"uniform float dim_brightness;\n"
+"uniform int kept_vertex;\n"
+"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
-" int r = (c >> 16) & 0xFF;\n"
-" int g = (c >> 8) & 0xFF;\n"
-" int b = (c >> 0) & 0xFF;\n"
+" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
+" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
+" if (dim_brightness >= 0.0)\n"
+" rgb = floor(rgb * dim_brightness);\n"
+" else if (layer < grey_below_layer)\n"
+" rgb = vec3(64.0);\n"
+" }\n"
" float f = 1.0 / 255.0f;\n"
-" return f * vec3(r, g, b);\n"
+" return f * rgb;\n"
"}\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -66,7 +83,8 @@ static const char* Segments_Vertex_Shader_ES =
"}\n"
"void main() {\n"
" int vertex_id = int(vertex_id_float);\n"
-" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, gl_InstanceID), 0).r);\n"
+" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
+" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
" int id_b = id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_b), 0).xyz;\n"
@@ -151,24 +169,87 @@ static const char* Segments_Vertex_Shader_ES =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
-" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
+" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
" color = color_base * (ambient + emission);\n"
" 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";
+// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
+static const char* Segments_Shadow_Caster_Vertex_Shader_ES =
+"#version 300 es\n"
+"precision lowp usampler2D;\n"
+"const vec3 UP = vec3(0, 0, 1);\n"
+"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
+"uniform mat4 view_matrix;\n"
+"uniform mat4 projection_matrix;\n"
+"uniform sampler2D position_tex;\n"
+"uniform sampler2D height_width_angle_tex;\n"
+"uniform usampler2D segment_index_tex;\n"
+"uniform bool reverse_order;\n"
+"uniform int instances_count;\n"
+"out vec3 world_position;\n"
+"ivec2 tex_coord(sampler2D sampler, int id) {\n"
+" ivec2 tex_size = textureSize(sampler, 0);\n"
+" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
+"}\n"
+"ivec2 tex_coord_u(usampler2D sampler, int id) {\n"
+" ivec2 tex_size = textureSize(sampler, 0);\n"
+" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
+"}\n"
+"void main() {\n"
+" int segment = gl_VertexID / 6;\n"
+" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
+" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
+" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
+" int id = corner < 2 ? id_a : id_a + 1;\n"
+" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
+" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_a + 1), 0).xyz;\n"
+" vec3 line = pos_b - pos_a;\n"
+" float line_len = length(line);\n"
+" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
+" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
+" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
+" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
+" vec3 side_dir = cross(to_light, line_dir);\n"
+" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
+" vec2 height_width = texelFetch(height_width_angle_tex, tex_coord(height_width_angle_tex, id), 0).xy;\n"
+" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
+" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
+" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
+" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
+" world_position = pos;\n"
+" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
+"}\n";
+
+// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
+static const char* Segments_Shadow_Caster_Fragment_Shader_ES =
+"#version 300 es\n"
+"precision highp float;\n"
+"uniform vec4 clipping_plane;\n"
+"in vec3 world_position;\n"
+"void main() {\n"
+" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
+" discard;\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"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
-"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
+"uniform vec3 light_top_dir;\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
@@ -179,23 +260,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), light_top_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 +297,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,16 +356,28 @@ 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"
-"vec3 decode_color(float color) {\n"
+"out float clipping_plane_dot;\n"
+// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
+"uniform ivec2 lit_layers;\n"
+"uniform int grey_below_layer;\n"
+"uniform float dim_brightness;\n"
+"uniform int kept_vertex;\n"
+"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
-" int r = (c >> 16) & 0xFF;\n"
-" int g = (c >> 8) & 0xFF;\n"
-" int b = (c >> 0) & 0xFF;\n"
+" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
+" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
+" if (dim_brightness >= 0.0)\n"
+" rgb = floor(rgb * dim_brightness);\n"
+" else if (layer < grey_below_layer)\n"
+" rgb = vec3(64.0);\n"
+" }\n"
" float f = 1.0 / 255.0f;\n"
-" return f * vec3(r, g, b);\n"
+" return f * rgb;\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -264,9 +402,11 @@ 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"
+" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
@@ -275,8 +415,11 @@ static const char* Options_Fragment_Shader_ES =
"#version 300 es\n"
"precision highp float;\n"
"in vec3 color;\n"
+"in float clipping_plane_dot;\n"
"out vec4 fragment_color;\n"
"void main() {\n"
+" if (clipping_plane_dot < 0.0)\n"
+" discard;\n"
" fragment_color = vec4(color, 1.0);\n"
"}\n";
diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp
index 61d7310508..089f1f7e7b 100644
--- a/src/libvgcode/src/Viewer.cpp
+++ b/src/libvgcode/src/Viewer.cpp
@@ -52,11 +52,21 @@ void Viewer::set_shadow_map(int texture_unit, const Mat4x4& light_view_projectio
m_impl->set_shadow_map(texture_unit, light_view_projection, intensity, texel_size);
}
+void Viewer::set_light_top_dir(const Vec3& direction)
+{
+ m_impl->set_light_top_dir(direction);
+}
+
void Viewer::set_tone(float exposure, float saturation)
{
m_impl->set_tone(exposure, saturation);
}
+void Viewer::set_clipping_plane(const std::array& plane)
+{
+ m_impl->set_clipping_plane(plane);
+}
+
EViewType Viewer::get_view_type() const
{
return m_impl->get_view_type();
diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp
index 8212717931..ee38594817 100644
--- a/src/libvgcode/src/ViewerImpl.cpp
+++ b/src/libvgcode/src/ViewerImpl.cpp
@@ -349,7 +349,7 @@ void ViewerImpl::TextureData::init(size_t vertices_count)
m_tex_ids = std::vector(m_count);
}
-void ViewerImpl::TextureData::set_positions(const std::vector& positions)
+void ViewerImpl::TextureData::set_positions(const std::vector& positions)
{
if (m_count == 0)
return;
@@ -383,17 +383,17 @@ void ViewerImpl::TextureData::set_positions(const std::vector& positions)
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
- glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGB, GL_FLOAT, &positions[offset]));
+ glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_FLOAT, &positions[offset]));
m_tex_ids[i].positions.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
- glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGB, GL_FLOAT, nullptr));
- glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(w), static_cast(h - 1), GL_RGB, GL_FLOAT, &positions[offset]));
- glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast(h - 1), static_cast(remaining % w), 1, GL_RGB, GL_FLOAT, &positions[offset + w * (h - 1)]));
+ glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_FLOAT, nullptr));
+ glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(w), static_cast(h - 1), GL_RGBA, GL_FLOAT, &positions[offset]));
+ glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast(h - 1), static_cast(remaining % w), 1, GL_RGBA, GL_FLOAT, &positions[offset + w * (h - 1)]));
m_tex_ids[i].positions.second = w * (h - 1) + remaining % w;
}
- m_positions_size += m_tex_ids[i].positions.second * sizeof(Vec3);
+ m_positions_size += m_tex_ids[i].positions.second * sizeof(Vec4);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity: 0;
}
@@ -402,7 +402,7 @@ void ViewerImpl::TextureData::set_positions(const std::vector& positions)
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
-void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector& heights_widths_angles)
+void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector& heights_widths_angles)
{
if (m_count == 0)
return;
@@ -436,17 +436,17 @@ void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector&
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
- glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
+ glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_FLOAT, &heights_widths_angles[offset]));
m_tex_ids[i].heights_widths_angles.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
- glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast(w), static_cast(h), 0, GL_RGB, GL_FLOAT, nullptr));
- glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(w), static_cast(h - 1), GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
- glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast(h - 1), static_cast(remaining % w), 1, GL_RGB, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
+ glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast(w), static_cast(h), 0, GL_RGBA, GL_FLOAT, nullptr));
+ glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(w), static_cast(h - 1), GL_RGBA, GL_FLOAT, &heights_widths_angles[offset]));
+ glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast(h - 1), static_cast(remaining % w), 1, GL_RGBA, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
m_tex_ids[i].heights_widths_angles.second = w * (h - 1) + remaining % w;
}
- m_height_width_angle_size += m_tex_ids[i].heights_widths_angles.second * sizeof(Vec3);
+ m_height_width_angle_size += m_tex_ids[i].heights_widths_angles.second * sizeof(Vec4);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity : 0;
}
@@ -732,6 +732,21 @@ ViewerImpl::ViewerImpl()
reset_default_options_colors();
}
+void ViewerImpl::SegmentsUniforms::init(unsigned int shader_id)
+{
+ view_matrix = glGetUniformLocation(shader_id, "view_matrix");
+ projection_matrix = glGetUniformLocation(shader_id, "projection_matrix");
+ camera_position = glGetUniformLocation(shader_id, "camera_position");
+ positions_tex = glGetUniformLocation(shader_id, "position_tex");
+ height_width_angle_tex = glGetUniformLocation(shader_id, "height_width_angle_tex");
+ colors_tex = glGetUniformLocation(shader_id, "color_tex");
+ segment_index_tex = glGetUniformLocation(shader_id, "segment_index_tex");
+ reverse_order = glGetUniformLocation(shader_id, "reverse_order");
+ instances_count = glGetUniformLocation(shader_id, "instances_count");
+ // ORCA: section view
+ clipping_plane = glGetUniformLocation(shader_id, "clipping_plane");
+}
+
void ViewerImpl::init(const std::string& opengl_context_version)
{
if (m_initialized)
@@ -756,15 +771,7 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_segments_shader_id = init_shader("segments", Segments_Vertex_Shader, Segments_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
- m_uni_segments_view_matrix_id = glGetUniformLocation(m_segments_shader_id, "view_matrix");
- m_uni_segments_projection_matrix_id = glGetUniformLocation(m_segments_shader_id, "projection_matrix");
- m_uni_segments_camera_position_id = glGetUniformLocation(m_segments_shader_id, "camera_position");
- m_uni_segments_positions_tex_id = glGetUniformLocation(m_segments_shader_id, "position_tex");
- m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
- m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
- m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
- m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
- m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
+ m_uni_segments.init(m_segments_shader_id);
// ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
@@ -772,15 +779,29 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel");
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");
+ m_uni_segments_light_top_dir_id = glGetUniformLocation(m_segments_shader_id, "light_top_dir");
+ m_uni_segments_layer_colors.init(m_segments_shader_id);
+ glcheck();
+ assert(m_uni_segments.view_matrix != -1 &&
+ m_uni_segments.projection_matrix != -1 &&
+ m_uni_segments.camera_position != -1 &&
+ m_uni_segments.positions_tex != -1 &&
+ m_uni_segments.height_width_angle_tex != -1 &&
+ m_uni_segments.colors_tex != -1 &&
+ m_uni_segments.segment_index_tex != -1 &&
+ m_uni_segments.reverse_order != -1 &&
+ m_uni_segments.instances_count != -1);
+
+ // ORCA: realistic view
+#ifdef ENABLE_OPENGL_ES
+ m_segments_caster_shader_id = init_shader("segments_shadow_caster", Segments_Shadow_Caster_Vertex_Shader_ES, Segments_Shadow_Caster_Fragment_Shader_ES);
+#else
+ m_segments_caster_shader_id = init_shader("segments_shadow_caster", Segments_Shadow_Caster_Vertex_Shader, Segments_Shadow_Caster_Fragment_Shader);
+#endif // ENABLE_OPENGL_ES
+ m_uni_segments_caster.init(m_segments_caster_shader_id);
+ // The caster pulls its vertices from gl_VertexID, but a core profile needs a vertex array bound to draw.
+ glsafe(glGenVertexArrays(1, &m_segments_caster_vao_id));
glcheck();
- assert(m_uni_segments_view_matrix_id != -1 &&
- m_uni_segments_projection_matrix_id != -1 &&
- m_uni_segments_camera_position_id != -1 &&
- m_uni_segments_positions_tex_id != -1 &&
- m_uni_segments_height_width_angle_tex_id != -1 &&
- m_uni_segments_colors_tex_id != -1 &&
- m_uni_segments_segment_index_tex_id != -1);
m_segment_template.init();
@@ -797,6 +818,9 @@ 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");
+ m_uni_options_layer_colors.init(m_options_shader_id);
glcheck();
assert(m_uni_options_view_matrix_id != -1 &&
m_uni_options_projection_matrix_id != -1 &&
@@ -870,6 +894,14 @@ void ViewerImpl::shutdown()
glsafe(glDeleteProgram(m_segments_shader_id));
m_segments_shader_id = 0;
}
+ if (m_segments_caster_shader_id != 0) {
+ glsafe(glDeleteProgram(m_segments_caster_shader_id));
+ m_segments_caster_shader_id = 0;
+ }
+ if (m_segments_caster_vao_id != 0) {
+ glsafe(glDeleteVertexArrays(1, &m_segments_caster_vao_id));
+ m_segments_caster_vao_id = 0;
+ }
m_initialized = false;
OpenGLWrapper::unload_opengl();
}
@@ -890,7 +922,7 @@ void ViewerImpl::reset()
for (std::vector& times : m_layer_start_times)
std::vector().swap(times);
std::vector().swap(m_layer_first_vertex);
- std::vector().swap(m_colors_scratch);
+ m_layers_in_vertex_order = false;
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
@@ -920,9 +952,8 @@ void ViewerImpl::reset()
// On some graphic cards texture buffers using GL_RGB32F format do not work, see:
// https://dev.prusa3d.com/browse/SPE-2411
// https://github.com/prusa3d/PrusaSlicer/issues/12908
-// To let all drivers be happy, we use GL_RGBA32F format, so we need to add an extra (currently unused) float
+// To let all drivers be happy, we use GL_RGBA32F format, so we need to add an extra float
// to position and heights_widths_angles vectors
-using Vec4 = std::array;
static void extract_pos_and_or_hwa(const std::vector& vertices, float travels_radius, float wipes_radius, BitSet<>& valid_lines_bitset,
std::vector* positions = nullptr, std::vector* heights_widths_angles = nullptr, bool update_bitset = false) {
@@ -959,8 +990,8 @@ static void extract_pos_and_or_hwa(const std::vector& vertices, floa
}
if (positions != nullptr) {
- // the last component is a dummy float to comply with GL_RGBA32F format
- Vec4 position = { v.position[0], v.position[1], v.position[2], 0.0f };
+ // ORCA: the last component, there to comply with GL_RGBA32F format, carries the layer the shaders grey and dim by
+ Vec4 position = { v.position[0], v.position[1], v.position[2], static_cast(v.layer_id) };
if (move_type == EMoveType::Extrude)
// push down extrusion vertices by half height to render them at the right z
position[2] -= 0.5f * v.height;
@@ -1076,6 +1107,8 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
}
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
+ m_layers_in_vertex_order = std::is_sorted(m_vertices.begin(), m_vertices.end(),
+ [](const PathVertex& a, const PathVertex& b) { return a.layer_id < b.layer_id; });
// the running time at each layer's first vertex, summed in vertex order so that
// get_estimated_time_at() matches a full accumulation exactly
@@ -1270,17 +1303,6 @@ static float encode_color(const Color& color) {
return static_cast(i_color);
}
-// ORCA: returns the encoded color scaled towards black by 'brightness', preserving its hue.
-// 1.0 = no change, 0.0 = black.
-static float encode_color_dimmed(const Color& color, float brightness) {
- const int r = static_cast(color[0] * brightness);
- const int g = static_cast(color[1] * brightness);
- const int b = static_cast(color[2] * brightness);
- const int i_color = r << 16 | g << 8 | b;
- return static_cast(i_color);
-}
-
-
void ViewerImpl::update_colors_texture()
{
#if !defined(ENABLE_OPENGL_ES)
@@ -1288,6 +1310,32 @@ void ViewerImpl::update_colors_texture()
return;
#endif // ENABLE_OPENGL_ES
+ // ORCA: the colors of the vertices alone; the layers greyed or dimmed around the one the sliders
+ // show are left to the shaders, see set_layer_colors(), so that a slider step uploads nothing.
+ #ifdef ENABLE_OPENGL_ES
+ if (!m_vertices_colors.empty())
+ // update gpu buffer for colors
+ m_texture_data.set_colors(m_vertices_colors);
+ #else
+ m_colors_tex_size = m_vertices_colors.size() * sizeof(float);
+
+ // update gpu buffer for colors
+ glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
+ glsafe(glBufferData(GL_TEXTURE_BUFFER, m_vertices_colors.size() * sizeof(float), m_vertices_colors.data(), GL_STATIC_DRAW));
+ glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
+ #endif // ENABLE_OPENGL_ES
+}
+
+void ViewerImpl::LayerColorsUniforms::init(unsigned int shader_id)
+{
+ lit_layers = glGetUniformLocation(shader_id, "lit_layers");
+ grey_below_layer = glGetUniformLocation(shader_id, "grey_below_layer");
+ dim_brightness = glGetUniformLocation(shader_id, "dim_brightness");
+ kept_vertex = glGetUniformLocation(shader_id, "kept_vertex");
+}
+
+void ViewerImpl::set_layer_colors(const LayerColorsUniforms& uni) const
+{
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
@@ -1306,43 +1354,15 @@ void ViewerImpl::update_colors_texture()
const bool dim_previous_layers = m_settings.dim_previous_layers && m_settings.top_layer_only_view_range &&
!m_layers.empty() && (inspecting_top_layer || inspecting_bottom_layer);
- // Based on current settings and slider position, we might want to render some
- // vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
- // Reused across calls: this runs on every slider tick, and the allocation alone is
- // 4 bytes per vertex of the whole print each time.
- std::vector& colors = m_colors_scratch;
- colors.resize(m_vertices_colors.size());
- assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
- for (size_t i=0; i(layers_range[0]) : -1,
+ dim_previous_layers && inspecting_top_layer ? static_cast(layers_range[1]) : -1));
+ glsafe(glUniform1i(uni.grey_below_layer, color_top_layer_only ? static_cast(top_layer_id) : 0));
+ glsafe(glUniform1f(uni.dim_brightness, dim_previous_layers ? m_settings.dim_previous_layers_brightness : -1.0f));
+ glsafe(glUniform1i(uni.kept_vertex, m_settings.spiral_vase_mode ? static_cast(m_view_range.get_enabled()[0]) : -1));
}
-
void ViewerImpl::update_colors()
{
@@ -1362,7 +1382,7 @@ void ViewerImpl::update_colors()
// Recalculate "normal" colors of all the vertices for current view settings.
// If some part of the preview should be rendered in dark grey, it is taken
- // care of in update_colors_texture. That is to avoid the need to recalculate
+ // care of in the shaders, see set_layer_colors(). That is to avoid the need to recalculate
// the "normal" color on every slider move.
for (size_t i = 0; i < m_vertices.size(); ++i)
m_vertices_colors[i] = encode_color(get_vertex_color(m_vertices[i]));
@@ -1449,8 +1469,6 @@ void ViewerImpl::set_layers_view_range(Interval::value_type min, Interval::value
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
- //m_settings.update_colors = true;
- update_colors_texture();
}
void ViewerImpl::toggle_top_layer_only_view_range()
@@ -1459,8 +1477,6 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
- //m_settings.update_colors = true;
- update_colors_texture();
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
@@ -1469,9 +1485,6 @@ void ViewerImpl::set_dim_previous_layers(bool value)
if (m_settings.dim_previous_layers == value)
return;
m_settings.dim_previous_layers = value;
- // defer the actual color/texture rebuild to the next render(), when the GL context is current
- // (this may be toggled from the Preferences dialog, outside the canvas context)
- m_settings.update_colors = true;
}
// ORCA: set how bright the darkened layers are rendered, 1.0 = unchanged, 0.0 = black
@@ -1481,7 +1494,6 @@ void ViewerImpl::set_dim_previous_layers_brightness(float value)
if (m_settings.dim_previous_layers_brightness == value)
return;
m_settings.dim_previous_layers_brightness = value;
- m_settings.update_colors = true;
}
std::vector ViewerImpl::get_time_modes() const
@@ -1589,8 +1601,6 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
update_view_full_range();
m_view_range.set_visible(min, max);
update_enabled_entities();
- //m_settings.update_colors = true;
- update_colors_texture();
}
float ViewerImpl::get_estimated_time_at(size_t id) const
@@ -1815,7 +1825,6 @@ size_t ViewerImpl::get_used_cpu_memory() const
for (const std::vector& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
- ret += STDVEC_MEMSIZE(m_colors_scratch, float);
ret += m_valid_lines_bitset.size_in_bytes_cpu();
ret += m_height_range.size_in_bytes_cpu();
ret += m_width_range.size_in_bytes_cpu();
@@ -1908,6 +1917,11 @@ void ViewerImpl::update_view_full_range()
}
auto last_it = first_it;
+ // ORCA: skip straight to the layer above the range rather than walking there
+ if (m_layers_in_vertex_order && layers_range[1] + 1 < m_layer_first_vertex.size())
+ last_it = std::max(first_it, m_vertices.begin() + m_layer_first_vertex[layers_range[1] + 1]);
+ else if (m_layers_in_vertex_order)
+ last_it = m_vertices.end();
while (last_it != m_vertices.end() && last_it->layer_id <= layers_range[1]) {
++last_it;
}
@@ -1947,12 +1961,14 @@ void ViewerImpl::update_view_full_range()
if (m_settings.top_layer_only_view_range) {
const Interval& full_range = m_view_range.get_full();
- auto top_first_it = m_vertices.begin() + full_range[0];
- bool shortened = false;
- while (top_first_it != m_vertices.end() && (top_first_it->layer_id < layers_range[1] || !is_visible(*top_first_it, m_settings))) {
+ const auto range_first_it = m_vertices.begin() + full_range[0];
+ auto top_first_it = range_first_it;
+ // ORCA: skip straight to the top layer rather than walking there
+ if (m_layers_in_vertex_order)
+ top_first_it = std::max(top_first_it, m_vertices.begin() + m_layer_first_vertex[layers_range[1]]);
+ while (top_first_it != m_vertices.end() && (top_first_it->layer_id < layers_range[1] || !is_visible(*top_first_it, m_settings)))
++top_first_it;
- shortened = true;
- }
+ const bool shortened = top_first_it != range_first_it;
if (shortened)
--top_first_it;
@@ -2034,7 +2050,7 @@ void ViewerImpl::update_color_ranges()
void ViewerImpl::update_heights_widths()
{
#ifdef ENABLE_OPENGL_ES
- std::vector heights_widths_angles;
+ std::vector heights_widths_angles;
heights_widths_angles.reserve(m_vertices.size());
extract_pos_and_or_hwa(m_vertices, m_travels_radius, m_wipes_radius, m_valid_lines_bitset, nullptr, &heights_widths_angles);
m_texture_data.set_heights_widths_angles(heights_widths_angles);
@@ -2066,7 +2082,10 @@ void ViewerImpl::update_heights_widths()
void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& camera_position)
{
- if (m_segments_shader_id == 0)
+ // ORCA: realistic view. The shadow caster pass draws depth only.
+ const unsigned int shader_id = m_rendering_shadow_casters ? m_segments_caster_shader_id : m_segments_shader_id;
+ const SegmentsUniforms& uni = m_rendering_shadow_casters ? m_uni_segments_caster : m_uni_segments;
+ if (shader_id == 0)
return;
#ifdef ENABLE_OPENGL_ES
@@ -2083,41 +2102,54 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
glcheck();
- glsafe(glUseProgram(m_segments_shader_id));
+ glsafe(glUseProgram(shader_id));
- glsafe(glUniform1i(m_uni_segments_positions_tex_id, 0));
- glsafe(glUniform1i(m_uni_segments_height_width_angle_tex_id, 1));
- glsafe(glUniform1i(m_uni_segments_colors_tex_id, 2));
- glsafe(glUniform1i(m_uni_segments_segment_index_tex_id, 3));
- glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
- glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
- glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
- // The segments come in print order, bottom layer first. Seen from above, that is back to front,
- // and every hidden fragment is shaded before the one that covers it. Drawing them last to first
- // lets the depth test reject the hidden ones instead. The camera looks down when the world's
- // up axis points towards it, which is the view matrix's (2, 2) entry being positive.
- const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
- glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
-#ifndef ENABLE_OPENGL_ES
- glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast(m_enabled_segments_count)));
-#endif // ENABLE_OPENGL_ES
- // ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
- // with the lookup off.
- glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
- glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
- glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity));
- glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
- 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(glUniform1i(uni.positions_tex, 0));
+ glsafe(glUniform1i(uni.height_width_angle_tex, 1));
+ glsafe(glUniform1i(uni.colors_tex, 2));
+ glsafe(glUniform1i(uni.segment_index_tex, 3));
+ glsafe(glUniformMatrix4fv(uni.view_matrix, 1, GL_FALSE, view_matrix.data()));
+ glsafe(glUniformMatrix4fv(uni.projection_matrix, 1, GL_FALSE, projection_matrix.data()));
+ glsafe(glUniform3fv(uni.camera_position, 1, camera_position.data()));
+ // ORCA: view_matrix(2,2) > 0 is looking down, where the last segments printed are the nearest.
+ const bool reverse_order = view_matrix[10] > 0.0f;
+ glsafe(glUniform1i(uni.reverse_order, reverse_order ? 1 : 0));
+ glsafe(glUniform4fv(uni.clipping_plane, 1, m_clipping_plane.data()));
+ if (!m_rendering_shadow_casters) {
+ // ORCA: realistic view
+ glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
+ glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
+ glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_shadow_intensity));
+ glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
+ glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
+ glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
+ glsafe(glUniform3fv(m_uni_segments_light_top_dir_id, 1, m_light_top_dir.data()));
+ set_layer_colors(m_uni_segments_layer_colors);
+ }
glsafe(glDisable(GL_CULL_FACE));
+ auto draw = [this, &uni](size_t count) {
+ glsafe(glUniform1i(uni.instances_count, static_cast(count)));
+ if (!m_rendering_shadow_casters) {
+ m_segment_template.render(count);
+ return;
+ }
+ // ORCA: realistic view. 6 vertices per caster ribbon.
+ int curr_vertex_array = 0;
+ glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
+ glsafe(glBindVertexArray(m_segments_caster_vao_id));
+ glsafe(glDrawArrays(GL_TRIANGLES, 0, static_cast(6 * count)));
+ glsafe(glBindVertexArray(curr_vertex_array));
+ };
+
#ifdef ENABLE_OPENGL_ES
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
- for (size_t i = 0; i < m_texture_data.get_count(); ++i) {
+ const size_t tex_count = m_texture_data.get_count();
+ for (size_t n = 0; n < tex_count; ++n) {
+ const size_t i = reverse_order ? tex_count - 1 - n : n;
const auto [id, count] = m_texture_data.get_enabled_segments_tex_id(i);
if (count == 0)
continue;
@@ -2129,7 +2161,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_colors_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_2D, id));
- m_segment_template.render(count);
+ draw(count);
}
#else
std::array curr_bound_texture = { 0, 0, 0, 0 };
@@ -2152,7 +2184,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
- m_segment_template.render(m_enabled_segments_count);
+ draw(m_enabled_segments_count);
#endif // ENABLE_OPENGL_ES
if (curr_cull_face)
@@ -2197,6 +2229,8 @@ 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()));
+ set_layer_colors(m_uni_options_layer_colors);
glsafe(glEnable(GL_CULL_FACE));
diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp
index 707f0f6bb7..335fb5ef52 100644
--- a/src/libvgcode/src/ViewerImpl.hpp
+++ b/src/libvgcode/src/ViewerImpl.hpp
@@ -89,6 +89,9 @@ public:
// caller decides which of the two it varies with the realistic view setting.
//
void set_tone(float exposure, float saturation);
+ // ORCA: section view, see Viewer::set_clipping_plane()
+ void set_clipping_plane(const std::array& plane) { m_clipping_plane = plane; }
+ void set_light_top_dir(const Vec3& direction) { m_light_top_dir = direction; }
EViewType get_view_type() const { return m_settings.view_type; }
void set_view_type(EViewType type);
@@ -262,9 +265,10 @@ private:
//
std::vector m_layer_first_vertex;
//
- // Scratch buffer for update_colors_texture(), kept alive across slider steps
+ // ORCA: whether the layer ids never decrease along the vertices, so that each layer's vertices
+ // follow m_layer_first_vertex. Not so for a print by object, whose layers start over per object.
//
- std::vector m_colors_scratch;
+ bool m_layers_in_vertex_order{ false };
//
// Detected travel moves times
//
@@ -347,6 +351,9 @@ private:
// OpenGL shaders ids
//
unsigned int m_segments_shader_id{ 0 };
+ // ORCA: realistic view. Depth-only ribbons for the shadow caster pass, and the empty vertex array they draw with.
+ unsigned int m_segments_caster_shader_id{ 0 };
+ unsigned int m_segments_caster_vao_id{ 0 };
unsigned int m_options_shader_id{ 0 };
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
unsigned int m_cog_marker_shader_id{ 0 };
@@ -355,24 +362,44 @@ private:
//
// Caches for OpenGL uniforms id for segments shader
//
- int m_uni_segments_view_matrix_id{ -1 };
- int m_uni_segments_projection_matrix_id{ -1 };
- int m_uni_segments_camera_position_id{ -1 };
- int m_uni_segments_positions_tex_id{ -1 };
- int m_uni_segments_height_width_angle_tex_id{ -1 };
- int m_uni_segments_colors_tex_id{ -1 };
- int m_uni_segments_segment_index_tex_id{ -1 };
- int m_uni_segments_reverse_order_id{ -1 };
- int m_uni_segments_instance_count_id{ -1 };
+ // ORCA: the ones the shaded and the shadow caster programs share.
+ struct SegmentsUniforms
+ {
+ int view_matrix{ -1 };
+ int projection_matrix{ -1 };
+ int camera_position{ -1 };
+ int positions_tex{ -1 };
+ int height_width_angle_tex{ -1 };
+ int colors_tex{ -1 };
+ int segment_index_tex{ -1 };
+ int reverse_order{ -1 };
+ int instances_count{ -1 };
+ int clipping_plane{ -1 };
+
+ void init(unsigned int shader_id);
+ };
+ SegmentsUniforms m_uni_segments;
+ SegmentsUniforms m_uni_segments_caster;
int m_uni_segments_shadow_map_id{ -1 };
int m_uni_segments_shadow_light_vp_id{ -1 };
int m_uni_segments_shadow_intensity_id{ -1 };
int m_uni_segments_shadow_map_texel_id{ -1 };
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_light_top_dir_id{ -1 };
+ // ORCA: the layers greyed or dimmed around the one the sliders show, in the segments and options shaders.
+ struct LayerColorsUniforms
+ {
+ int lit_layers{ -1 };
+ int grey_below_layer{ -1 };
+ int dim_brightness{ -1 };
+ int kept_vertex{ -1 };
+
+ void init(unsigned int shader_id);
+ };
+ LayerColorsUniforms m_uni_segments_layer_colors;
//
- // Caches for OpenGL uniforms id for options shader
+ // Caches for OpenGL uniforms id for options shader
//
int m_uni_options_view_matrix_id{ -1 };
int m_uni_options_projection_matrix_id{ -1 };
@@ -380,6 +407,8 @@ 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 };
+ LayerColorsUniforms m_uni_options_layer_colors;
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
//
// Caches for OpenGL uniforms id for cog marker shader
@@ -403,8 +432,8 @@ private:
{
public:
void init(size_t vertices_count);
- void set_positions(const std::vector& positions);
- void set_heights_widths_angles(const std::vector& heights_widths_angles);
+ void set_positions(const std::vector& positions);
+ void set_heights_widths_angles(const std::vector& heights_widths_angles);
void set_colors(const std::vector& colors);
void set_enabled_segments(const std::vector& enabled_segments);
void set_enabled_options(const std::vector& enabled_options);
@@ -512,8 +541,7 @@ private:
//
// ORCA: realistic view. Shadow map state set by set_shadow_map(), consumed by the segments
- // shader. m_rendering_shadow_casters forces the intensity to 0 for the depth pass, which
- // must not sample the very map it is writing.
+ // shader. m_rendering_shadow_casters switches render_segments() to the depth-only program.
//
// Defaults past the four texture units render_segments() binds itself, so the sampler never
// aliases one of the buffer textures before the owner of the map has said where it lives.
@@ -529,6 +557,11 @@ private:
//
float m_exposure{ 1.0f };
float m_saturation{ 1.0f };
+ // ORCA: the light the segments shader shades with, in eye space.
+ Vec3 m_light_top_dir{ -0.4574957f, 0.4574957f, 0.7624929f };
+
+ // ORCA: section view
+ std::array m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f };
void apply_pending_updates();
void update_view_full_range();
@@ -536,6 +569,8 @@ private:
void update_heights_widths();
void render_segments(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& camera_position);
void render_options(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
+ // ORCA: the program using uni must be current.
+ void set_layer_colors(const LayerColorsUniforms& uni) const;
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
void render_cog_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
void render_tool_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 7d6c3f86aa..7ab35f8637 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -507,6 +507,10 @@ set(SLIC3R_GUI_SOURCES
GUI/SceneRaycaster.hpp
GUI/SceneCache.cpp
GUI/SceneCache.hpp
+ GUI/FrameProfiler.cpp
+ GUI/FrameProfiler.hpp
+ GUI/SceneBenchmark.cpp
+ GUI/SceneBenchmark.hpp
GUI/PartSkipCommon.hpp
GUI/PartSkipDialog.cpp
GUI/PartSkipDialog.hpp
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index 0ba85775bd..a46707ff98 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -1127,8 +1127,17 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
);
}
else if (type == GLVolumeCollection::ERenderType::Opaque && list.size() > 1) {
+ // Orca: nearest first after the selected ones, so the depth test skips shading hidden surfaces.
+ for (GLVolumeWithIdAndZ& volume : list) {
+ volume.second.second = volume.first->transformed_bounding_box().transformed(view_matrix).max(2);
+ }
+
std::sort(list.begin(), list.end(),
- [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; }
+ [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool {
+ if (v1.first->selected != v2.first->selected)
+ return v1.first->selected;
+ return v1.second.second > v2.second.second;
+ }
);
}
diff --git a/src/slic3r/GUI/FrameProfiler.cpp b/src/slic3r/GUI/FrameProfiler.cpp
new file mode 100644
index 0000000000..d79a0eef99
--- /dev/null
+++ b/src/slic3r/GUI/FrameProfiler.cpp
@@ -0,0 +1,153 @@
+#include "libslic3r/libslic3r.h"
+#include "FrameProfiler.hpp"
+
+#include "3DScene.hpp"
+
+#include
+
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+void FrameProfiler::begin_frame()
+{
+ m_recording = nullptr;
+ // GL 3.3 or ARB_timer_query
+ if (glQueryCounter == nullptr)
+ return;
+
+ collect(false);
+ Frame& frame = m_frames[m_next];
+ // Every frame in flight still waits for the GPU: skip this one rather than stall.
+ if (frame.pending)
+ return;
+
+ if (frame.queries[0] == 0)
+ glsafe(::glGenQueries(GLsizei(frame.queries.size()), frame.queries.data()));
+ glsafe(::glQueryCounter(frame.queries[0], GL_TIMESTAMP));
+ frame.count = 0;
+ frame.averaged = m_averaging;
+ m_recording = &frame;
+ m_last_mark = std::chrono::steady_clock::now();
+}
+
+void FrameProfiler::mark(const char* name)
+{
+ if (m_recording == nullptr || m_recording->count == MAX_SECTIONS)
+ return;
+
+ Frame& frame = *m_recording;
+ const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+ glsafe(::glQueryCounter(frame.queries[2 * frame.count + 1], GL_TIMESTAMP));
+ // Else the GPU time would include the CPU time spent until the driver flushes on its own.
+ glsafe(::glFlush());
+ // Sent with the next section's commands, so the time the GPU waits for them is not billed to it.
+ if (frame.count + 1 < MAX_SECTIONS)
+ glsafe(::glQueryCounter(frame.queries[2 * frame.count + 2], GL_TIMESTAMP));
+ frame.names[frame.count] = name;
+ frame.cpu_ms[frame.count] = std::chrono::duration(now - m_last_mark).count();
+ ++frame.count;
+ m_last_mark = now;
+}
+
+void FrameProfiler::end_frame()
+{
+ if (m_recording == nullptr)
+ return;
+
+ m_recording->pending = m_recording->count > 0;
+ m_recording = nullptr;
+ m_next = (m_next + 1) % FRAMES_IN_FLIGHT;
+}
+
+void FrameProfiler::collect(bool wait)
+{
+ constexpr double SMOOTHING = 0.1;
+
+ // Oldest first: a frame whose results are not in yet holds back the newer ones too.
+ for (size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
+ Frame& frame = m_frames[(m_next + i) % FRAMES_IN_FLIGHT];
+ if (!frame.pending)
+ continue;
+
+ if (!wait) {
+ GLint available = 0;
+ glsafe(::glGetQueryObjectiv(frame.queries[2 * frame.count - 1], GL_QUERY_RESULT_AVAILABLE, &available));
+ if (available == 0)
+ break;
+ }
+
+ std::array stamps{};
+ for (size_t j = 0; j < 2 * frame.count; ++j)
+ glsafe(::glGetQueryObjectui64v(frame.queries[j], GL_QUERY_RESULT, &stamps[j]));
+ frame.pending = false;
+
+ const bool averaged = frame.averaged && m_averaging;
+ if (averaged)
+ ++m_averaged_frames;
+ std::vector sections;
+ sections.reserve(frame.count);
+ for (size_t j = 0; j < frame.count; ++j) {
+ Section section{ frame.names[j], frame.cpu_ms[j], stamps[2 * j + 1] > stamps[2 * j] ? double(stamps[2 * j + 1] - stamps[2 * j]) * 1e-6 : 0.0 };
+ if (averaged) {
+ auto sum = std::find_if(m_sums.begin(), m_sums.end(), [§ion](const Section& s) { return std::strcmp(s.name, section.name) == 0; });
+ if (sum == m_sums.end())
+ m_sums.push_back(section);
+ else {
+ sum->cpu_ms += section.cpu_ms;
+ sum->gpu_ms += section.gpu_ms;
+ }
+ }
+ for (const Section& prev : m_sections) {
+ if (std::strcmp(prev.name, section.name) == 0) {
+ section.cpu_ms = prev.cpu_ms + SMOOTHING * (section.cpu_ms - prev.cpu_ms);
+ section.gpu_ms = prev.gpu_ms + SMOOTHING * (section.gpu_ms - prev.gpu_ms);
+ break;
+ }
+ }
+ sections.push_back(section);
+ }
+ m_sections = std::move(sections);
+ }
+}
+
+void FrameProfiler::start_averaging()
+{
+ m_averaging = true;
+ m_sums.clear();
+ m_averaged_frames = 0;
+}
+
+std::vector FrameProfiler::finish_averaging(bool wait)
+{
+ if (wait && glQueryCounter != nullptr)
+ collect(true);
+ std::vector averages = std::move(m_sums);
+ for (Section& section : averages) {
+ section.cpu_ms /= double(m_averaged_frames);
+ section.gpu_ms /= double(m_averaged_frames);
+ }
+ m_averaging = false;
+ m_sums.clear();
+ m_averaged_frames = 0;
+ return averages;
+}
+
+void FrameProfiler::reset()
+{
+ for (Frame& frame : m_frames) {
+ if (frame.queries[0] != 0)
+ glsafe(::glDeleteQueries(GLsizei(frame.queries.size()), frame.queries.data()));
+ frame = Frame();
+ }
+ m_recording = nullptr;
+ m_sections.clear();
+ m_averaging = false;
+ m_sums.clear();
+ m_averaged_frames = 0;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/FrameProfiler.hpp b/src/slic3r/GUI/FrameProfiler.hpp
new file mode 100644
index 0000000000..cfda8a4d8a
--- /dev/null
+++ b/src/slic3r/GUI/FrameProfiler.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+// CPU and GPU time spent in the named sections of a frame, shown under the FPS overlay and averaged
+// by the scene benchmark. A section runs from the previous mark to the one naming it; its GPU time
+// leaves out any wait for the CPU to send its commands. GPU times are read back a few frames late,
+// so profiling never waits on the GPU.
+class FrameProfiler
+{
+public:
+ struct Section
+ {
+ const char* name{ nullptr };
+ double cpu_ms{ 0.0 };
+ double gpu_ms{ 0.0 };
+ };
+
+ void begin_frame();
+ void mark(const char* name);
+ void end_frame();
+ // The sections of the last frame read back, smoothed over the previous ones.
+ const std::vector& sections() const { return m_sections; }
+ // Averages the sections of the frames begun from now on, until finish_averaging().
+ void start_averaging();
+ bool is_averaging() const { return m_averaging; }
+ // With wait, first reads back the averaged frames still on the GPU, with the context current.
+ std::vector finish_averaging(bool wait);
+ // Frees the queries, with the context current.
+ void reset();
+
+private:
+ static constexpr size_t FRAMES_IN_FLIGHT = 4;
+ static constexpr size_t MAX_SECTIONS = 32;
+
+ struct Frame
+ {
+ // A begin and an end timestamp per section.
+ std::array queries{};
+ std::array names{};
+ std::array cpu_ms{};
+ size_t count{ 0 };
+ bool pending{ false };
+ bool averaged{ false };
+ };
+
+ void collect(bool wait);
+
+ std::array m_frames;
+ Frame* m_recording{ nullptr };
+ size_t m_next{ 0 };
+ std::chrono::steady_clock::time_point m_last_mark;
+ std::vector m_sections;
+ bool m_averaging{ false };
+ std::vector m_sums;
+ size_t m_averaged_frames{ 0 };
+};
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
index f2bf134653..53e5a2f4d0 100644
--- a/src/slic3r/GUI/GCodeViewer.cpp
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -83,6 +83,7 @@
#include
#include
+#include
#include
#include
#include
@@ -2220,6 +2221,26 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
}
}
+size_t GCodeViewer::shadow_casters_signature() const
+{
+ size_t hash = m_viewer.get_vertices_count();
+ const libvgcode::Interval& visible = m_viewer.get_view_visible_range();
+ const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
+ for (size_t value : { size_t(visible[0]), size_t(visible[1]), size_t(layers[0]), size_t(layers[1]) })
+ boost::hash_combine(hash, value);
+ for (double value : { m_paths_bounding_box.min.x(), m_paths_bounding_box.min.y(), m_paths_bounding_box.min.z(),
+ m_paths_bounding_box.max.x(), m_paths_bounding_box.max.y(), m_paths_bounding_box.max.z() })
+ boost::hash_combine(hash, value);
+ boost::hash_combine(hash, m_viewer.is_top_layer_only_view_range());
+ for (size_t i = 0; i < libvgcode::GCODE_EXTRUSION_ROLES_COUNT; ++i)
+ boost::hash_combine(hash, m_viewer.is_extrusion_role_visible(libvgcode::EGCodeExtrusionRole(i)));
+ boost::hash_combine(hash, m_viewer.is_option_visible(libvgcode::EOptionType::Travels));
+ boost::hash_combine(hash, m_viewer.is_option_visible(libvgcode::EOptionType::Wipes));
+ for (float value : m_clipping_plane)
+ boost::hash_combine(hash, value);
+ return hash;
+}
+
void GCodeViewer::render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position)
{
if (!has_data())
@@ -2238,11 +2259,26 @@ void GCodeViewer::set_shadow_map(int texture_unit, const Transform3d& light_view
intensity, texel_size);
}
+void GCodeViewer::set_light_top_dir(const Vec3d& direction)
+{
+ m_viewer.set_light_top_dir(libvgcode::convert(static_cast(direction.cast())));
+}
+
void GCodeViewer::set_tone(float exposure, float saturation)
{
m_viewer.set_tone(exposure, saturation);
}
+void GCodeViewer::set_clipping_plane(const ClippingPlane& plane)
+{
+ // Flipped to match ClippingPlane::distance().
+ const Vec3f normal = -plane.get_normal().cast();
+ m_clipping_plane = plane.is_active() ?
+ std::array{ normal.x(), normal.y(), normal.z(), float(plane.get_offset()) } :
+ std::array{ 0.0f, 0.0f, 0.0f, 1.0f };
+ m_viewer.set_clipping_plane(m_clipping_plane);
+}
+
void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin)
{
if (m_viewer.get_extrusion_roles().empty())
diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp
index ae1dfc3167..83a69cffe5 100644
--- a/src/slic3r/GUI/GCodeViewer.hpp
+++ b/src/slic3r/GUI/GCodeViewer.hpp
@@ -354,6 +354,8 @@ mutable bool m_no_render_path { false };
bool m_is_dark = false;
libvgcode::Viewer m_viewer;
+ // ORCA: section view, as the viewer has it. What it cuts away casts no shadow.
+ std::array m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f };
bool m_loaded_as_preview{ false };
public:
@@ -394,6 +396,9 @@ 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);
+ void set_light_top_dir(const Vec3d& direction);
//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);
@@ -416,6 +421,10 @@ public:
std::vector get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
const std::array &get_layers_z_range() const { return m_viewer.get_layers_view_range(); }
+ size_t get_vertices_count() const { return m_viewer.get_vertices_count(); }
+ size_t get_layers_count() const { return m_viewer.get_layers_count(); }
+ // ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
+ size_t shadow_casters_signature() const;
const SequentialView& get_sequential_view() const { return m_sequential_view; }
void update_sequential_view_current(unsigned int first, unsigned int last);
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index cc8155ee0d..44c1c2ab8b 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1303,6 +1303,7 @@ GLCanvas3D::~GLCanvas3D()
{
if (_set_current()) {
m_scene_cache.reset();
+ m_frame_profiler.reset();
if (m_fxaa_texture_id != 0) {
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
m_fxaa_texture_id = 0;
@@ -1324,8 +1325,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 +1478,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 +1629,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) {
@@ -2126,7 +2167,7 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
wxGetApp().imgui()->new_frame();
- if (m_picking_enabled) {
+ if (m_picking_enabled && !m_benchmarking) {
if (m_rectangle_selection.is_dragging())
// picking pass using rectangle selection
_rectangular_selection_picking_pass();
@@ -2151,22 +2192,30 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
+ // Only frames that redraw the scene are profiled.
+ if (!reuse_scene && (_is_render_timings_enabled() || m_frame_profiler.is_averaging()))
+ m_frame_profiler.begin_frame();
+ Slic3r::ScopeGuard profiler_guard([this]() { m_frame_profiler.end_frame(); });
if (!reuse_scene) {
_render_scene(camera, cnv_size);
if (!overlay_tick)
m_render_stats.increment_scene_fps_counter();
}
- if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview)
+ if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
// BBS: GUI refactor: add canvas size as parameters
_render_gcode_overlay(cnv_size.get_width(), cnv_size.get_height());
+ m_frame_profiler.mark("legend");
+ }
// draw overlays
_render_overlays();
+ m_frame_profiler.mark("ui");
const int current_fps = m_render_stats.get_fps_and_reset_if_needed();
- if (_is_fps_overlay_enabled()) {
+ if (_is_fps_overlay_enabled() || _is_render_timings_enabled())
_render_fps_overlay(current_fps);
+ if (_is_fps_overlay_enabled()) {
// The timer requests an overlay-only frame a second from now. A frame it requested
// re-arms it only while a count is above zero.
if (!overlay_tick || current_fps > 0 || m_render_stats.get_scene_fps() > 0)
@@ -2264,12 +2313,14 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width(), bottom_margin, right_margin);
wxGetApp().plater()->get_dailytips()->render();
}
+ m_frame_profiler.mark("notifications");
ImDrawData* draw_data = wxGetApp().imgui()->end_frame();
std::optional signature;
if (_is_frame_skipping_enabled())
signature = _overlay_signature(draw_data);
+ m_frame_profiler.mark("imgui end");
if (reuse_scene) {
// A reused scene under an unchanged overlay is the frame already on screen.
@@ -2279,13 +2330,16 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
}
_render_overlay_toolbars();
+ m_frame_profiler.mark("toolbars");
wxGetApp().imgui()->render(draw_data);
+ m_frame_profiler.mark("imgui draw");
// On Wayland, eglSwapBuffers blocks when the canvas is hidden or
// occluded. Skip the swap to avoid stalling the render loop.
if (m_canvas->IsShownOnScreen()) {
m_canvas->SwapBuffers();
+ m_frame_profiler.mark("swap");
if (!overlay_tick)
m_render_stats.increment_fps_counter();
m_presented_signature = signature;
@@ -2330,32 +2384,44 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// floating would read as a rendering fault rather than a deliberate view option.
if (show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
+ m_frame_profiler.mark("bed");
if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
+ m_frame_profiler.mark("plates");
//BBS: add outline logic
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
+ m_frame_profiler.mark("shadows");
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
+ m_frame_profiler.mark("objects");
_render_sla_slices();
_render_selection();
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
+ m_frame_profiler.mark("transparent");
}
/* 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();
+ m_frame_profiler.mark("objects");
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
+ m_frame_profiler.mark("bed");
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
+ m_frame_profiler.mark("plates");
// Realistic view: the print casts a shadow onto the plate here as it does in View3D.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
+ m_frame_profiler.mark("shadows");
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
+ m_frame_profiler.mark("gcode");
}
/* assemble render*/
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
@@ -2372,6 +2438,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// BBS: add outline logic
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
+ m_frame_profiler.mark("objects");
}
_render_sequential_clearance();
@@ -2394,12 +2461,17 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
+ m_frame_profiler.mark("gizmos");
- if (_is_ssao_enabled())
+ if (_is_ssao_enabled()) {
_render_ssao_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()));
+ m_frame_profiler.mark("ssao");
+ }
- if (_is_fxaa_enabled())
+ if (_is_fxaa_enabled()) {
_render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()));
+ m_frame_profiler.mark("fxaa");
+ }
// Design tab: interactive 2D sketch overlay, drawn over the scene but
// beneath the UI overlays (toolbars, labels).
@@ -2409,6 +2481,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
#endif
_capture_scene_cache(camera);
+ m_frame_profiler.mark("cache");
}
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
@@ -3129,7 +3202,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
@@ -3402,7 +3474,7 @@ void GLCanvas3D::unbind_event_handlers()
void GLCanvas3D::on_idle(wxIdleEvent& evt)
{
- if (!m_initialized)
+ if (!m_initialized || m_benchmarking)
return;
// Toolbar states, notifications and ImGui's own layout settling only touch the overlay.
@@ -3845,12 +3917,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) {
@@ -3919,7 +3997,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
}
}
@@ -3939,6 +4018,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);
@@ -3984,25 +4065,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;
}
@@ -8037,7 +8114,51 @@ int GLCanvas3D::_get_effective_fps_cap() const
bool GLCanvas3D::_is_fps_overlay_enabled() const
{
- return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
+ return !m_benchmarking && wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
+}
+
+// Above the front left of the plate.
+static const Vec3d STATIC_LIGHT_DIR = Vec3d(-0.4, -0.6, 1.0).normalized();
+
+std::optional GLCanvas3D::_static_light_dir_eye() const
+{
+ if (!_is_realistic_view_enabled() || _shadow_mode() != EShadowMode::Static)
+ return std::nullopt;
+ const Matrix3d view_rot = wxGetApp().plater()->get_camera().get_view_matrix().matrix().block<3, 3>(0, 0);
+ return Vec3d((view_rot * STATIC_LIGHT_DIR).normalized());
+}
+
+GLCanvas3D::EShadowMode GLCanvas3D::_shadow_mode() const
+{
+ const std::string mode = wxGetApp().app_config != nullptr ? wxGetApp().app_config->get(SETTING_OPENGL_REALISTIC_SHADOWS) : std::string();
+ return mode == "static" ? EShadowMode::Static : mode == "orbit" ? EShadowMode::Orbit : EShadowMode::Off;
+}
+
+size_t GLCanvas3D::_shadow_casters_signature(bool toolpath_casters) const
+{
+ if (toolpath_casters)
+ return m_gcode_viewer.shadow_casters_signature();
+
+ size_t hash = 1;
+ for (const GLVolume* volume : m_volumes.volumes) {
+ if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
+ continue;
+ boost::hash_combine(hash, volume);
+ boost::hash_combine(hash, volume->model.vertices_count());
+ boost::hash_combine(hash, volume->model.indices_count());
+ const BoundingBoxf3& bb = volume->model.get_bounding_box();
+ for (double value : { bb.min.x(), bb.min.y(), bb.min.z(), bb.max.x(), bb.max.y(), bb.max.z() })
+ boost::hash_combine(hash, value);
+ const Transform3d world = volume->world_matrix();
+ for (int i = 0; i < 16; ++i)
+ boost::hash_combine(hash, world.matrix().data()[i]);
+ }
+ return hash;
+}
+
+bool GLCanvas3D::_is_render_timings_enabled() const
+{
+ return !m_benchmarking && wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_RENDER_TIMINGS);
}
bool GLCanvas3D::_is_scene_cache_enabled() const
@@ -8072,14 +8193,22 @@ bool GLCanvas3D::_is_frame_skipping_enabled() const
void GLCanvas3D::_render_fps_overlay(int fps) const
{
- if (fps < 0)
+ const bool show_fps = _is_fps_overlay_enabled() && fps >= 0;
+ const std::vector& sections = m_frame_profiler.sections();
+ const bool show_timings = _is_render_timings_enabled() && !sections.empty();
+ if (!show_fps && !show_timings)
return;
ImGuiWrapper& imgui = *wxGetApp().imgui();
const float margin = 10.0f * get_scale();
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
- ImGui::SetNextWindowPos(ImVec2(display_size.x - margin, margin), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
+ ImVec2 pos(display_size.x - margin, margin);
+ // The Preview legend takes the top-right corner.
+ if (const ImGuiWindow* legend = ImGui::FindWindowByName("Legend"); m_canvas_type == ECanvasType::CanvasPreview && legend != nullptr && legend->Active)
+ pos = ImVec2(legend->Pos.x - margin, legend->Pos.y);
+ ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.35f);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * get_scale());
imgui.begin(
std::string("###fps_overlay"),
ImGuiWindowFlags_AlwaysAutoResize |
@@ -8089,10 +8218,38 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs);
- imgui.text(std::string("FPS: ") + std::to_string(fps));
- // The subset of those frames that redrew the scene rather than reusing the cached one.
- imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
+ if (show_fps) {
+ imgui.text(std::string("FPS: ") + std::to_string(fps));
+ ImGui::SameLine();
+ // The subset of those frames that redrew the scene rather than reusing the cached one.
+ imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
+ }
+ if (show_timings && ImGui::BeginTable("frame_sections", 3, ImGuiTableFlags_SizingFixedFit)) {
+ ImGui::TableSetupColumn("ms");
+ ImGui::TableSetupColumn("CPU");
+ ImGui::TableSetupColumn("GPU");
+ ImGui::TableHeadersRow();
+ double cpu_ms = 0.0;
+ double gpu_ms = 0.0;
+ auto row = [](const char* name, double cpu, double gpu) {
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0);
+ ImGui::TextUnformatted(name);
+ ImGui::TableSetColumnIndex(1);
+ ImGui::Text("%.2f", cpu);
+ ImGui::TableSetColumnIndex(2);
+ ImGui::Text("%.2f", gpu);
+ };
+ for (const FrameProfiler::Section& section : sections) {
+ row(section.name, section.cpu_ms, section.gpu_ms);
+ cpu_ms += section.cpu_ms;
+ gpu_ms += section.gpu_ms;
+ }
+ row("total", cpu_ms, gpu_ms);
+ ImGui::EndTable();
+ }
imgui.end();
+ ImGui::PopStyleVar();
}
void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height)
@@ -8442,7 +8599,8 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
return;
if (!_is_realistic_view_enabled())
return;
- if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS))
+ const EShadowMode mode = _shadow_mode();
+ if (mode == EShadowMode::Off)
return;
// The preview canvas holds no volumes of its own for FFF. Once slicing has run its printed
@@ -8457,10 +8615,11 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
- // Light direction (same as used in shading and plate shading)
+ // Orbit: the light used for shading, fixed to the camera. Static: a world light, also used for shading.
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
- const Vec3d dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
+ const Vec3d dir_to_light = mode == EShadowMode::Static ? STATIC_LIGHT_DIR :
+ Vec3d((view_rot.transpose() * light_dir_eye).normalized());
// Bounding box of the printable objects (the shadow casters).
BoundingBoxf3 obj_bb;
@@ -8512,11 +8671,19 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
lmin = lmin.cwiseMin(lp);
lmax = lmax.cwiseMax(lp);
};
+ // The plate area a shadow can reach: the casters' footprint and where their corners land on z = 0.
+ Vec2d reach_min(DBL_MAX, DBL_MAX);
+ Vec2d reach_max(-DBL_MAX, -DBL_MAX);
+ auto reach = [&](const Vec3d& p) {
+ reach_min = reach_min.cwiseMin(Vec2d(p.x(), p.y()));
+ reach_max = reach_max.cwiseMax(Vec2d(p.x(), p.y()));
+ };
for (int i = 0; i < 8; ++i) {
const Vec3d corner((i & 1) ? obj_bb.max.x() : obj_bb.min.x(),
(i & 2) ? obj_bb.max.y() : obj_bb.min.y(),
(i & 4) ? obj_bb.max.z() : obj_bb.min.z());
enclose(corner);
+ reach(corner);
// Where this corner's shadow lands on z = 0, clamped to the plate so a grazing angle
// (t -> infinity) stays bounded.
if (ray_dir.z() < -1e-6) {
@@ -8526,6 +8693,7 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
s.y() = std::min(std::max(s.y(), plate_bb.min.y()), plate_bb.max.y());
s.z() = 0.0;
enclose(s);
+ reach(s);
}
}
@@ -8559,6 +8727,11 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
// Create / resize the depth texture and FBO
const unsigned int size = 2048;
+
+ // Padded by the filter's 2 texel reach, stretched where the light grazes the plate.
+ const double reach_margin = 3.0 * 2.0 * std::max(halfx, halfy) / size / std::max(0.1, std::abs(dir_to_light.z()));
+ m_shadow_plate_bounds = { float(reach_min.x() - reach_margin), float(reach_min.y() - reach_margin),
+ float(reach_max.x() + reach_margin), float(reach_max.y() + reach_margin) };
if (m_shadow_map_texture_id == 0) {
glsafe(::glGenTextures(1, &m_shadow_map_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
@@ -8578,66 +8751,80 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
if (m_shadow_map_fbo == 0)
glsafe(::glGenFramebuffers(1, &m_shadow_map_fbo));
- // Save OpenGL state that we will modify
- GLint prev_viewport[4] = { 0, 0, 0, 0 };
- glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
- GLint prev_fbo = 0;
- glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
- GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
- glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
- const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
- GLint prev_depth_func = GL_LESS;
- glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
- GLboolean prev_depth_mask = GL_TRUE;
- glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
- glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
- glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
- glsafe(::glDrawBuffer(GL_NONE));
- glsafe(::glReadBuffer(GL_NONE));
-
- if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo)));
- m_shadow_map_valid = false;
- } else {
- glsafe(::glViewport(0, 0, size, size));
- glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
- glsafe(::glEnable(GL_DEPTH_TEST));
- glsafe(::glDepthMask(GL_TRUE));
- glsafe(::glDepthFunc(GL_LESS));
- glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
- glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
- glsafe(::glPolygonOffset(4.0f, 4.0f));
- glsafe(::glDisable(GL_CULL_FACE));
-
- if (toolpath_casters)
- m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
- // Only this branch draws through "flat"; the toolpaths bring their own program.
- else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
- shader->start_using();
- shader->set_uniform("projection_matrix", Transform3d(light_proj));
- for (GLVolume* volume : m_volumes.volumes) {
- if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
- continue;
- const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
- shader->set_uniform("view_model_matrix", view_model);
- volume->model.render(shader);
- }
- shader->stop_using();
- }
-
- // Restore state
- glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
- glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
- if (prev_cull)
- glsafe(::glEnable(GL_CULL_FACE));
- else
- glsafe(::glDisable(GL_CULL_FACE));
- glsafe(::glDepthFunc(prev_depth_func));
- glsafe(::glDepthMask(prev_depth_mask));
- glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo)));
- glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
-
+ // A static light keeps the map until its frustum or the casters change.
+ size_t cache_key = 0;
+ if (mode == EShadowMode::Static) {
+ cache_key = _shadow_casters_signature(toolpath_casters);
+ for (int i = 0; i < 16; ++i)
+ boost::hash_combine(cache_key, m_shadow_light_vp.matrix().data()[i]);
+ }
+ if (cache_key != 0 && cache_key == m_shadow_map_key)
m_shadow_map_valid = true;
+ else {
+ m_shadow_map_key = 0;
+ // Save OpenGL state that we will modify
+ GLint prev_viewport[4] = { 0, 0, 0, 0 };
+ glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
+ GLint prev_fbo = 0;
+ glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
+ GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
+ glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
+ const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
+ GLint prev_depth_func = GL_LESS;
+ glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
+ GLboolean prev_depth_mask = GL_TRUE;
+ glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
+ glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
+ glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
+ glsafe(::glDrawBuffer(GL_NONE));
+ glsafe(::glReadBuffer(GL_NONE));
+
+ if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo)));
+ m_shadow_map_valid = false;
+ } else {
+ glsafe(::glViewport(0, 0, size, size));
+ glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ glsafe(::glDepthMask(GL_TRUE));
+ glsafe(::glDepthFunc(GL_LESS));
+ glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
+ glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
+ glsafe(::glPolygonOffset(4.0f, 4.0f));
+ glsafe(::glDisable(GL_CULL_FACE));
+
+ if (toolpath_casters)
+ m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
+ // Only this branch draws through "flat"; the toolpaths bring their own program.
+ else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
+ shader->start_using();
+ shader->set_uniform("projection_matrix", Transform3d(light_proj));
+ for (GLVolume* volume : m_volumes.volumes) {
+ if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
+ continue;
+ const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
+ shader->set_uniform("view_model_matrix", view_model);
+ volume->model.render(shader);
+ }
+ shader->stop_using();
+ }
+
+ // Restore state
+ glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
+ glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
+ if (prev_cull)
+ glsafe(::glEnable(GL_CULL_FACE));
+ else
+ glsafe(::glDisable(GL_CULL_FACE));
+ glsafe(::glDepthFunc(prev_depth_func));
+ glsafe(::glDepthMask(prev_depth_mask));
+ glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast(prev_fbo)));
+ glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
+
+ m_shadow_map_valid = true;
+ }
+ if (m_shadow_map_valid)
+ m_shadow_map_key = cache_key;
}
} else {
m_shadow_map_valid = false;
@@ -8737,6 +8924,7 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
plate_shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
plate_shader->set_uniform("shadow_intensity", 0.35f);
plate_shader->set_uniform("shadow_map_texel", 1.0f / static_cast(m_shadow_map_size));
+ plate_shader->set_uniform("shadow_bounds", m_shadow_plate_bounds);
m_plate_shadow_mask.render(plate_shader);
plate_shader->stop_using();
@@ -8766,7 +8954,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.
@@ -8809,10 +8997,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 {
@@ -8866,6 +9051,10 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
}
else
shader->set_uniform("shadow_intensity", 0.0f);
+ const std::optional static_light = _static_light_dir_eye();
+ shader->set_uniform("use_static_light", static_light.has_value());
+ if (static_light.has_value())
+ shader->set_uniform("static_light_dir", *static_light);
const Size& cvn_size = get_canvas_size();
{
@@ -8923,6 +9112,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
}
}
+ _render_section_view_caps();
break;
}
case GLVolumeCollection::ERenderType::Transparent:
@@ -8947,12 +9137,6 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
// IMEX ghosts are transparent and share the same shader/camera state;
// render them right after the main transparent pass while the shader is still bound.
_render_imex_ghosts();
- 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;
}
}
@@ -8966,6 +9150,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
}
+ shader->set_uniform("use_static_light", false);
shader->stop_using();
}
@@ -8973,6 +9158,42 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
}
+void GLCanvas3D::_render_section_view_caps()
+{
+ // A gizmo with its own clipper draws its own cut faces.
+ const ClippingPlane plane = m_gizmos.get_clipping_plane().has_value() ? ClippingPlane::ClipsNothing() : _get_section_view_plane();
+ if (!plane.is_active() || m_model == nullptr) {
+ m_section_view_caps.clear();
+ return;
+ }
+
+ // Last frame's clippers are reused, so unchanged cuts are not recomputed.
+ std::map caps;
+ for (const GLVolume* volume : m_volumes.volumes) {
+ if (!volume->is_active || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0 ||
+ volume->object_idx() >= int(m_model->objects.size()))
+ continue;
+ const ModelObject* object = m_model->objects[volume->object_idx()];
+ if (volume->volume_idx() >= int(object->volumes.size()))
+ continue;
+ // Skip volumes the plane misses.
+ const BoundingBoxf3& box = volume->transformed_bounding_box();
+ if (std::abs(plane.distance(box.center())) > 0.5 * box.size().dot(plane.get_normal().cwiseAbs()))
+ continue;
+
+ auto node = m_section_view_caps.extract(volume);
+ MeshClipper& clipper = node ? caps.insert(std::move(node)).position->second : caps[volume];
+ clipper.set_mesh(object->volumes[volume->volume_idx()]->mesh().its);
+ clipper.set_plane(plane);
+ clipper.set_transformation(Geometry::Transformation(volume->world_matrix()));
+ // No cut face below the plate.
+ if (m_canvas_type != CanvasAssembleView)
+ clipper.set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
+ clipper.render_cut({ 0.25f, 0.25f, 0.25f, 1.0f });
+ }
+ m_section_view_caps = std::move(caps);
+}
+
bool GLCanvas3D::_is_xray_view_active() const
{
if (m_canvas_type == ECanvasType::CanvasPreview || !wxGetApp().plater()->is_show_xray())
@@ -9109,8 +9330,9 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
// again - realistic view with at least one lossy pass on - else the lift would just clip.
const AppConfig* cfg = wxGetApp().app_config;
const bool lossy_passes = cfg != nullptr && _is_realistic_view_enabled() &&
- (cfg->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
+ (_shadow_mode() != EShadowMode::Off || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
m_gcode_viewer.set_tone(lossy_passes ? 1.1f : 1.0f, 1.15f);
+ m_gcode_viewer.set_light_top_dir(_static_light_dir_eye().value_or(Vec3d(-0.4574957, 0.4574957, 0.7624929)));
m_gcode_viewer.render_scene(canvas_width, canvas_height);
@@ -10091,7 +10313,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);
@@ -10257,6 +10503,76 @@ void GLCanvas3D::_render_canvas_toolbar()
ImGui::PopStyleVar(6);
imgui.end();
+
+ _render_section_view_panel(section_panel_pos);
+}
+
+void GLCanvas3D::_render_section_view_panel(const ImVec2& bottom_left)
+{
+ if (!m_section_view->panel_open)
+ return;
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ const float sc = get_scale();
+
+ ImGuiWrapper::push_toolbar_style(sc);
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, m_is_dark ? ImGuiWrapper::COL_TOOLBAR_BG_DARK : ImGuiWrapper::COL_TOOLBAR_BG);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.f * sc);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.f, 8.f) * sc);
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.f, 4.f) * sc);
+
+ ImGui::SetNextWindowPos(bottom_left, ImGuiCond_Always, ImVec2(0.f, 1.f));
+ imgui.begin(std::string("##CanvasSectionView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar);
+
+ const wxString alt = GUI::shortkey_alt_prefix();
+ const std::vector> tips = {
+ { alt + _L("Mouse wheel"), _L("Move section plane") },
+ { _L("Mouse wheel on icon"), _L("Move section plane") },
+ { _L("Right click on icon"), _L("Toggle section view") },
+ { _L("Middle click on icon"), _L("Set viewing angle") },
+ };
+ // render_tooltip_button() puts the tips below y plus the panel height; they go above the panel.
+ const ImGuiStyle& style = ImGui::GetStyle();
+ const float tips_h = tips.size() * (ImGui::GetTextLineHeight() + style.ItemSpacing.y) - style.ItemSpacing.y + 2.f * style.WindowPadding.y;
+ GLGizmoUtils::render_tooltip_button(&imgui, *this, tips, ImGui::GetWindowPos().x,
+ ImGui::GetWindowPos().y - style.ItemSpacing.y - tips_h - ImGui::GetContentRegionMax().y - ImGui::GetFrameHeight());
+
+ ImGui::SameLine();
+ ImGui::AlignTextToFramePadding();
+ imgui.text(_L("Section view"));
+
+ float ratio = float(get_section_view_ratio());
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(imgui.scaled(7.f));
+ bool changed = imgui.bbl_slider_float_style("##section_view", &ratio, 0.f, 1.f, "%.2f", 1.0f, true);
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(1.5f * imgui.get_slider_icon_size().x);
+ changed |= ImGui::BBLDragFloat("##section_view_input", &ratio, 0.05f, 0.0f, 0.0f, "%.2f");
+ if (changed)
+ set_section_view_ratio(ratio);
+
+ ImGui::SameLine();
+ if (imgui.button(_L("Set viewing angle")))
+ align_section_view_to_camera();
+
+ imgui.disabled_begin(!is_section_view_active());
+ ImGui::SameLine();
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.f, 0.f, 0.f));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.f, 0.f, 0.f, 0.f));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f);
+ if (imgui.button(wxString(ImGui::RevertBtn) + "##section_view_reset", _L("Reset")))
+ set_section_view_ratio(0.);
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor(3);
+ imgui.disabled_end();
+
+ imgui.end();
+
+ ImGui::PopStyleVar(3);
+ ImGui::PopStyleColor();
+ ImGuiWrapper::pop_toolbar_style();
}
void GLCanvas3D::_render_separator_toolbar_right() const
@@ -10504,17 +10820,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();
@@ -10524,8 +10835,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;
@@ -10540,34 +10849,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);
@@ -10991,12 +11272,42 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
return mouse_ray(mouse_pos).intersect_plane(0.0);
}
+ClippingPlane GLCanvas3D::_get_section_view_plane() const
+{
+ const SectionView& section_view = *m_section_view;
+ if (section_view.ratio == 0.)
+ return ClippingPlane::ClipsNothing();
+
+ // The owner's objects, so Preview cuts where Prepare does.
+ const GLCanvas3D& owner = section_view.owner != nullptr ? *section_view.owner : *this;
+ BoundingBoxf3 box = owner.volumes_bounding_box(true);
+ if (!box.defined)
+ box = owner.volumes_bounding_box();
+ // G-code without objects: its toolpaths, merged by corner as a flat box is undefined.
+ if (!box.defined && m_gcode_viewer.has_data()) {
+ box.merge(m_gcode_viewer.get_paths_bounding_box().min);
+ box.merge(m_gcode_viewer.get_paths_bounding_box().max);
+ }
+ if (!box.defined)
+ return ClippingPlane::ClipsNothing();
+
+ // Sweeps the bounding sphere from the camera side (0) to the far side (1), as ObjectClipper does.
+ const double radius = box.radius();
+ return ClippingPlane(section_view.normal, section_view.normal.dot(box.center()) + radius - 2. * radius * section_view.ratio);
+}
+
+ClippingPlane GLCanvas3D::_get_volumes_clipping_plane() const
+{
+ const std::optional gizmo_plane = m_gizmos.get_clipping_plane();
+ return gizmo_plane.has_value() ? *gizmo_plane : _get_section_view_plane().inverted_normal();
+}
+
ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const
{
// Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and
// invert the result into the convention expected by SceneRaycaster.
GLGizmoBase* current_gizmo = m_gizmos.get_current();
- return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
+ return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? _get_volumes_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
}
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 12da1f1cf6..f7fbf2dbca 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -37,6 +37,7 @@
#include "Camera.hpp"
#include "SceneRaycaster.hpp"
#include "SceneCache.hpp"
+#include "FrameProfiler.hpp"
#include "IMToolbar.hpp"
#include "slic3r/GUI/3DBed.hpp"
#include "libslic3r/Slicing.hpp"
@@ -619,6 +620,19 @@ private:
std::array m_clipping_planes;
ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
+ struct SectionView
+ {
+ explicit SectionView(const GLCanvas3D* owner) : owner(owner) {}
+ double ratio{ 0. }; // 0 = off
+ double last_ratio{ 0. }; // before it was switched off
+ Vec3d normal{ Vec3d::Zero() }; // zero until first aimed
+ bool panel_open{ false };
+ const GLCanvas3D* owner; // whose objects place the plane
+ };
+ std::shared_ptr m_section_view{ std::make_shared(this) };
+ std::map m_section_view_caps;
+ // Its release would open the hidden window menu on Windows.
+ bool m_alt_wheel_used{ false };
std::array m_sla_caps;
std::string m_sidebar_field;
// when true renders an extra frame by not resetting m_dirty to false
@@ -704,6 +718,8 @@ private:
bool m_reload_delayed;
RenderStats m_render_stats;
+ FrameProfiler m_frame_profiler;
+ bool m_benchmarking{ false };
std::chrono::time_point m_last_frame_start_time{ std::chrono::steady_clock::now() };
int m_imgui_undo_redo_hovered_pos{ -1 };
@@ -836,6 +852,10 @@ public:
unsigned int m_shadow_map_size{ 0 };
Transform3d m_shadow_light_vp{ Transform3d::Identity() };
bool m_shadow_map_valid{ false };
+ // Casters and light frustum the map was last rendered for, under a static light. 0 when none.
+ size_t m_shadow_map_key{ 0 };
+ // Plate rectangle a shadow can reach, min xy then max xy.
+ std::array m_shadow_plate_bounds{ { 0.0f, 0.0f, 0.0f, 0.0f } };
public:
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
~GLCanvas3D();
@@ -946,6 +966,14 @@ public:
void set_color_clip_plane(const Vec3d& cp_normal, double offset) { m_volumes.set_color_clip_plane(cp_normal, offset); }
void set_color_clip_plane_colors(const std::array& colors) { m_volumes.set_color_clip_plane_colors(colors); }
+ bool is_section_view_active() const { return m_section_view->ratio > 0.; }
+ double get_section_view_ratio() const { return m_section_view->ratio; }
+ const Vec3d& get_section_view_normal() const { return m_section_view->normal; }
+ void set_section_view_ratio(double ratio);
+ void toggle_section_view();
+ void align_section_view_to_camera();
+ void share_section_view(const GLCanvas3D& owner) { m_section_view = owner.m_section_view; }
+
void toggle_world_axes_visibility(bool force_show = false);
void refresh_camera_scene_box();
void set_color_by(const std::string& value);
@@ -1256,6 +1284,10 @@ public:
void schedule_extra_frame(int milliseconds);
+ // The scene benchmark draws every frame itself, without picking or the FPS and timings overlays.
+ void set_benchmarking(bool benchmarking) { m_benchmarking = benchmarking; }
+ FrameProfiler& get_frame_profiler() { return m_frame_profiler; }
+
int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
void force_main_toolbar_right_action(int item_id) { m_main_toolbar.force_right_action(item_id, *this); }
@@ -1371,6 +1403,12 @@ private:
bool _is_ssao_enabled() const;
int _get_effective_fps_cap() const;
bool _is_fps_overlay_enabled() const;
+ bool _is_render_timings_enabled() const;
+ enum class EShadowMode { Off, Static, Orbit };
+ EShadowMode _shadow_mode() const;
+ // Direction to the static shadow light in eye space, when it lights the scene.
+ std::optional _static_light_dir_eye() const;
+ size_t _shadow_casters_signature(bool toolpath_casters) const;
bool _is_scene_cache_enabled() const;
bool _is_scene_cacheable() const;
bool _is_frame_skipping_enabled() const;
@@ -1405,6 +1443,7 @@ private:
void _render_imex_ghosts_xray();
// IMEX ghost hover tooltip: filament swatch + label drawn as an ImGui overlay.
void _render_imex_ghost_tooltip();
+ void _render_section_view_caps();
void _render_wireframe_overlay();
bool _is_xray_view_active() const;
void _render_xray_volumes();
@@ -1431,6 +1470,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;
@@ -1462,6 +1502,11 @@ private:
Gesture
};
+ void _on_section_view_changed();
+ // In the ClippingPlane::is_point_clipped() convention.
+ ClippingPlane _get_section_view_plane() const;
+ // In the volume shaders' convention: the open gizmo's own plane, else the section view's.
+ ClippingPlane _get_volumes_clipping_plane() const;
// Orca: These helpers keep clipping, orbit pivots, and perspective-pan depth selection consistent.
ClippingPlane get_raycaster_clipping_plane() const;
bool is_bed_visible() const;
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 7eaa0aad85..6bd0797ac9 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -8631,8 +8631,8 @@ void GUI_App::open_preferences(PreferencesTab tab, const std::string& highlight_
{
// Render settings the canvas reads every frame; a change needs one redraw to show.
static constexpr const char* opengl_render_setting_keys[] = {
- SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SCENE_CACHE,
- SETTING_OPENGL_SKIP_IDENTICAL_FRAMES
+ SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SHOW_RENDER_TIMINGS,
+ SETTING_OPENGL_SCENE_CACHE, SETTING_OPENGL_SKIP_IDENTICAL_FRAMES, SETTING_OPENGL_REALISTIC_SHADOWS
};
std::vector previous_opengl_render_settings;
for (const char* key : opengl_render_setting_keys)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp
index 7f52b2cc0c..944cfaad7a 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp
@@ -85,7 +85,6 @@ bool GLGizmoBrimEars::on_init()
m_shortcut = Shortcut::GizmoBrimEars;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
- const wxString alt = GUI::shortkey_alt_prefix();
m_desc["brim_ear_radius"] = _L("Brim ear radius");
m_desc["max_angle"] = _L("Max angle");
@@ -96,13 +95,11 @@ bool GLGizmoBrimEars::on_init()
m_desc["create"] = _L("Create");
m_desc["auto_generate"] = _L("Auto-generate");
m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius");
- m_desc["section_view"] = _L("Section view");
m_shortcuts = {
{_L("Left mouse button"), _L("Add or Select")},
{_L("Right mouse button"), _L("Remove")},
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
- {alt + _L("Mouse wheel"), m_desc["section_view"]},
};
return true;
@@ -551,26 +548,6 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
apply_radius_change();
}
- if (action == SLAGizmoEventType::MouseWheelUp && alt_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::min(1., pos + 0.01);
- m_c->object_clipper()->set_position_by_ratio(pos, false, true);
- return true;
- }
-
- if (action == SLAGizmoEventType::MouseWheelDown && alt_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::max(0., pos - 0.01);
- m_c->object_clipper()->set_position_by_ratio(pos, false, true);
- return true;
- }
-
- // reset clipper position
- if (action == SLAGizmoEventType::ResetClippingPlane) {
- m_c->object_clipper()->set_position_by_ratio(-1., false);
- return true;
- }
-
return false;
}
@@ -692,8 +669,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
float space_size = m_imgui->get_style_scaling() * 8;
- std::vector text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
- m_desc["create"], m_desc["remove"]};
+ std::vector text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["create"],
+ m_desc["remove"]};
float widest_text = m_imgui->find_widest_text(text_list);
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
float input_text_size = m_imgui->scaled(10.0f);
@@ -782,21 +759,6 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
}
}
m_imgui->disabled_end();
-
- ImGui::Separator();
-
- ImGui::AlignTextToFramePadding();
- float clp_dist = float(m_c->object_clipper()->get_position());
- m_imgui->text(m_desc["section_view"]);
- ImGui::SameLine(caption_size);
- ImGui::PushItemWidth(slider_width);
- bool slider_clp_dist = m_imgui->bbl_slider_float_style("##section_view", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
- ImGui::SameLine(drag_left_width);
- ImGui::PushItemWidth(1.5 * slider_icon_width);
- bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
- if (slider_clp_dist || b_clp_dist_input) {
- m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true);
- }
ImGui::Separator();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index 6a6288a71c..7ed567ac33 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -112,36 +112,30 @@ bool GLGizmoFdmSupports::on_init()
m_desc["highlight_by_angle"] = _L("Highlight overhangs");
m_desc["tool_type"] = _L("Tool type");
m_desc["gap_fill"] = _L("Gap fill");
- m_desc["reset_direction"] = _L("Reset direction");
- m_desc["clipping_of_view"] = _L("Section view");
m_desc["cursor_size"] = _L("Brush size");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["gap_area"] = _L("Gap area");
const wxString ctrl = GUI::shortkey_ctrl_prefix();
- const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
std::pair enforce_shortcut = {_L("Left mouse button"), _L("Enforce supports")};
std::pair block_shortcut = {_L("Right mouse button"), _L("Block supports")};
std::pair remove_shortcut = {shift + _L("Left mouse button"), _L("Erase")};
- std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
m_shortcuts_brush = {
enforce_shortcut,
block_shortcut,
remove_shortcut,
- {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
- clipping_shortcut
+ {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
m_shortcuts_bucket_fill = {
enforce_shortcut,
block_shortcut,
remove_shortcut,
- {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
- clipping_shortcut
+ {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}
};
m_shortcuts_gap_fill = {
@@ -237,11 +231,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
- const float clipping_slider_left = m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float gap_fill_slider_left = m_imgui->calc_text_size(m_desc.at("gap_fill")).x + m_imgui->scaled(1.5f);
const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f);
- const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2;
const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f);
const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f);
@@ -254,14 +246,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float caption_max = 0.f;
float total_text_max = 0.f;
- for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
+ for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
- const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(reset_button_slider_left, std::max(std::max(cursor_slider_left, clipping_slider_left), std::max(highlight_slider_left, gap_fill_slider_left)))));
+ const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(cursor_slider_left, std::max(highlight_slider_left, gap_fill_slider_left))));
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
@@ -458,29 +450,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##angle_threshold_deg_input", &m_highlight_by_angle_threshold_deg, 0.05f, 0.0f, 0.0f, "%.2f");
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this]() {
- m_c->object_clipper()->set_position_by_ratio(-1., false);
- });
- }
- }
- auto clp_dist = float(m_c->object_clipper()->get_position());
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(sliders_width);
- bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
-
- ImGui::SameLine(drag_left_width + sliders_left_width);
- ImGui::PushItemWidth(1.5 * slider_icon_width);
- bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
-
- if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
-
ImGui::Separator();
render_tooltip_button(x, y);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
index 7f36e85e80..e03912456a 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
@@ -51,10 +51,8 @@ bool GLGizmoFuzzySkin::on_init()
m_shortcut = Shortcut::GizmoFuzzySkin;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
- const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
- m_desc["reset_direction"] = _L("Reset direction");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
@@ -62,7 +60,6 @@ bool GLGizmoFuzzySkin::on_init()
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_smart_fill"] = _L("Smart fill");
- m_desc["clipping_of_view"] = _L("Section view");
m_desc["cursor_size"] = _L("Brush size");
m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin");
m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin");
@@ -70,26 +67,22 @@ bool GLGizmoFuzzySkin::on_init()
std::pair add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]};
std::pair remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]};
- std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
m_shortcuts_brush = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
- {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
- clipping_shortcut
+ {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
m_shortcuts_triangle = {
add_fuzzy_skin_shortcut,
- remove_fuzzy_skin_shortcut,
- clipping_shortcut
+ remove_fuzzy_skin_shortcut
};
m_shortcuts_smart_fill = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
- {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
- clipping_shortcut
+ {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}
};
return true;
@@ -162,8 +155,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
- m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
@@ -187,9 +178,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
- const float circle_max_width = std::max(clipping_slider_left, cursor_slider_left);
-
- const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
+ const float sliders_left_width = std::max(smart_fill_slider_left, cursor_slider_left);
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const float empty_button_width = m_imgui->calc_button_size("").x;
@@ -259,10 +248,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
- ImGui::SameLine(circle_max_width);
+ ImGui::SameLine(cursor_slider_left);
ImGui::PushItemWidth(sliders_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
- ImGui::SameLine(drag_left_width + circle_max_width);
+ ImGui::SameLine(drag_left_width + cursor_slider_left);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::TriangleButtonIcon) {
@@ -291,30 +280,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f");
}
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this](){
- m_c->object_clipper()->set_position_by_ratio(-1., false);
- });
- }
- }
-
- auto clp_dist = float(m_c->object_clipper()->get_position());
- ImGui::SameLine(sliders_left_width);
-
- ImGui::PushItemWidth(sliders_width);
- bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
-
- ImGui::SameLine(drag_left_width + sliders_left_width);
- ImGui::PushItemWidth(1.5 * slider_icon_width);
- bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
- if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); }
-
ImGui::Separator();
render_tooltip_button(x, y);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
index 2a19cf3ee7..a08ded3003 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
@@ -118,8 +118,6 @@ bool GLGizmoMmuSegmentation::on_init()
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
- m_desc["clipping_of_view"] = _L("Section view");
- m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Brush size");
m_desc["cursor_type"] = _L("Brush shape");
m_desc["paint"] = _L("Paint");
@@ -146,14 +144,12 @@ bool GLGizmoMmuSegmentation::on_init()
std::pair paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
std::pair erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
- std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
std::pair toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]};
m_shortcuts_brush = {
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
- clipping_shortcut,
toggle_wireframe_shortcut
};
@@ -161,7 +157,6 @@ bool GLGizmoMmuSegmentation::on_init()
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
- clipping_shortcut,
toggle_wireframe_shortcut
};
@@ -397,8 +392,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
- m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
const float edge_detect_slider_left = m_imgui->calc_text_size(m_desc.at("edge_detection")).x + m_imgui->scaled(1.f);
@@ -413,18 +406,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
float caption_max = 0.f;
float total_text_max = 0.f;
- for (const auto &t : std::array{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range", "clipping_of_view"}) {
+ for (const auto &t : std::array{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
- const float circle_max_width = std::max(clipping_slider_left,cursor_slider_left);
- const float height_max_width = std::max(clipping_slider_left,height_range_slider_left);
const float sliders_left_width = std::max(smart_fill_slider_left,
- std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, std::max(height_range_slider_left,
- clipping_slider_left))))) + space_size;
+ std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, height_range_slider_left)))) + space_size;
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const int max_filament_items_per_line = 8;
@@ -658,28 +648,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
}
}
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- } else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); });
- }
- }
-
- auto clp_dist = float(m_c->object_clipper()->get_position());
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(sliders_width);
- bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
- ImGui::SameLine(drag_left_width + sliders_left_width);
- ImGui::PushItemWidth(1.5 * slider_icon_width);
- bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
-
- if (slider_clp_dist || b_clp_dist_input) {
- m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
- }
-
ImGui::Separator();
render_tooltip_button(x, y);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
index c9847afcde..75d2905a86 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
@@ -750,20 +750,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return true;
}
}
- else if (alt_down) {
- // BBS
- double pos = m_c->object_clipper()->get_position();
- pos = action == SLAGizmoEventType::MouseWheelDown
- ? std::max(0., pos - 0.01)
- : std::min(1., pos + 0.01);
- m_c->object_clipper()->set_position_by_ratio(pos, true);
- return true;
- }
- }
-
- if (action == SLAGizmoEventType::ResetClippingPlane) {
- m_c->object_clipper()->set_position_by_ratio(-1., false);
- return true;
}
if (action == SLAGizmoEventType::LeftDown
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
index 3ae5604226..1d6d31a047 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
@@ -46,11 +46,8 @@ bool GLGizmoSeam::on_init()
m_shortcut = Shortcut::GizmoSeam;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
- const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
- m_desc["clipping_of_view"] = _L("Section view");
- m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Brush size");
m_desc["tool_type"] = _L("Tool type");
m_desc["enforce"] = _L("Enforce seam");
@@ -64,8 +61,7 @@ bool GLGizmoSeam::on_init()
{_L("Left mouse button"), m_desc["enforce"]},
{_L("Right mouse button"), m_desc["block"]},
{shift + _L("Left mouse button"), m_desc["remove"]},
- {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
- {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}
+ {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
return true;
@@ -155,20 +151,17 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
- m_imgui->calc_text_size(m_desc.at("reset_direction")).x + ImGui::GetStyle().FramePadding.x * 2)
- + m_imgui->scaled(1.5f);
const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float empty_button_width = m_imgui->calc_button_size("").x;
float caption_max = 0.f;
float total_text_max = 0.f;
- for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
+ for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
- const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left);
+ const float sliders_left_width = cursor_size_slider_left;
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
const float sliders_width = m_imgui->scaled(7.0f);
@@ -242,28 +235,6 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only);
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this](){
- m_c->object_clipper()->set_position_by_ratio(-1., false);
- });
- }
- }
-
- auto clp_dist = float(m_c->object_clipper()->get_position());
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(sliders_width);
- bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
- ImGui::SameLine(drag_left_width + sliders_left_width);
- ImGui::PushItemWidth(1.5 * slider_icon_width);
- bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
- if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); }
-
ImGui::Separator();
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
index 18308b81b2..9d684cdb88 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
@@ -380,22 +380,19 @@ std::vector ObjectClipper::point_per_contour() const
}
-void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal)
+void ObjectClipper::set_position_by_ratio(double pos, const Vec3d& normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
-
- Vec3d normal;
- if(vertical_normal) {
- normal = {0, 0, 1};
- }else {
- //Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
- //if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
- // camera_dir(2) = 0;
-
- normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward();
+ if (pos == 0. || mo == nullptr || active_inst < 0) {
+ // No plane, so the raycasts are not clipped.
+ m_clp_ratio = 0.;
+ m_clp.reset();
+ get_pool()->get_canvas()->set_as_dirty();
+ return;
}
+
Vec3d center;
if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
@@ -410,9 +407,6 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool ver
float dist = normal.dot(center);
- if (pos < 0.)
- pos = m_clp_ratio;
-
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2 * m_active_inst_bb_radius)));
@@ -445,171 +439,5 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou
}
-using namespace AssembleViewDataObjects;
-AssembleViewDataPool::AssembleViewDataPool(GLCanvas3D* canvas)
- : m_canvas(canvas)
-{
- using c = AssembleViewDataID;
- m_data[c::ModelObjectsInfo].reset(new ModelObjectsInfo(this));
- m_data[c::ModelObjectsClipper].reset(new ModelObjectsClipper(this));
-}
-
-void AssembleViewDataPool::update(AssembleViewDataID required)
-{
- assert(check_dependencies(required));
- for (auto& [id, data] : m_data) {
- if (int(required) & int(AssembleViewDataID(id)))
- data->update();
- else
- if (data->is_valid())
- data->release();
- }
-}
-
-
-ModelObjectsInfo* AssembleViewDataPool::model_objects_info() const
-{
- ModelObjectsInfo* sel_info = dynamic_cast(m_data.at(AssembleViewDataID::ModelObjectsInfo).get());
- assert(sel_info);
- return sel_info->is_valid() ? sel_info : nullptr;
-}
-
-
-ModelObjectsClipper* AssembleViewDataPool::model_objects_clipper() const
-{
- ModelObjectsClipper* oc = dynamic_cast(m_data.at(AssembleViewDataID::ModelObjectsClipper).get());
- // ObjectClipper is used from outside the gizmos to report current clipping plane.
- // This function can be called when oc is nullptr.
- return (oc && oc->is_valid()) ? oc : nullptr;
-}
-
-#ifndef NDEBUG
-// Check the required resources one by one and return true if all
-// dependencies are met.
-bool AssembleViewDataPool::check_dependencies(AssembleViewDataID required) const
-{
- // This should iterate over currently required data. Each of them should
- // be asked about its dependencies and it must check that all dependencies
- // are also in required and before the current one.
- for (auto& [id, data] : m_data) {
- // in case we don't use this, the deps are irrelevant
- if (!(int(required) & int(AssembleViewDataID(id))))
- continue;
-
-
- AssembleViewDataID deps = data->get_dependencies();
- assert(int(deps) == (int(deps) & int(required)));
- }
-
-
-return true;
-}
-#endif // NDEBUG
-
-
-
-
-void ModelObjectsInfo::on_update()
-{
- if (!get_pool()->get_canvas()->get_model()->objects.empty()) {
- m_model_objects = get_pool()->get_canvas()->get_model()->objects;
- }
- else {
- m_model_objects.clear();
- }
-}
-
-void ModelObjectsInfo::on_release()
-{
- m_model_objects.clear();
-}
-
-//int ModelObjectsInfo::get_active_instance() const
-//{
-// const Selection& selection = get_pool()->get_canvas()->get_selection();
-// return selection.get_instance_idx();
-//}
-
-
-void ModelObjectsClipper::on_update()
-{
- const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects();
- if (model_objects.empty())
- return;
-
- // which mesh should be cut?
- std::vector meshes;
-
- if (meshes.empty())
- for (auto mo : model_objects) {
- for (const ModelVolume* mv : mo->volumes)
- meshes.push_back(&mv->mesh());
- }
-
- if (meshes != m_old_meshes) {
- m_clippers.clear();
- for (const TriangleMesh* mesh : meshes) {
- m_clippers.emplace_back(new MeshClipper);
- m_clippers.back()->set_mesh(mesh->its);
- }
- m_old_meshes = meshes;
-
- m_active_inst_bb_radius = get_pool()->get_canvas()->volumes_bounding_box().radius();
- }
-}
-
-
-void ModelObjectsClipper::on_release()
-{
- m_clippers.clear();
- m_old_meshes.clear();
- m_clp.reset();
- m_clp_ratio = 0.;
-
-}
-
-void ModelObjectsClipper::render_cut() const
-{
- if (m_clp_ratio == 0.)
- return;
- const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects();
-
- size_t clipper_id = 0;
- for (const ModelObject* mo : model_objects) {
- Geometry::Transformation assemble_objects_trafo = mo->instances[0]->get_assemble_transformation();
- auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly();
- for (const ModelVolume* mv : mo->volumes) {
- Geometry::Transformation vol_trafo = mv->get_transformation();
- Geometry::Transformation trafo = assemble_objects_trafo * vol_trafo;
- trafo.set_offset(trafo.get_offset() + vol_trafo.get_offset() * (GLVolume::explosion_ratio - 1.0) + offset_to_assembly * (GLVolume::explosion_ratio - 1.0));
-
- auto& clipper = m_clippers[clipper_id];
- clipper->set_plane(*m_clp);
- clipper->set_transformation(trafo);
- // BBS
- clipper->render_cut({0.25f, 0.25f, 0.25f, 1.0f});
-
- ++clipper_id;
- }
- }
-}
-
-
-void ModelObjectsClipper::set_position(double pos, bool keep_normal)
-{
- Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
- const Vec3d& center = get_pool()->get_canvas()->volumes_bounding_box().center();
- float dist = normal.dot(center);
-
- if (pos < 0.)
- pos = m_clp_ratio;
-
- m_clp_ratio = pos;
- m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius * GLVolume::explosion_ratio) - m_clp_ratio * 2 * m_active_inst_bb_radius * GLVolume::explosion_ratio)));
- get_pool()->get_canvas()->set_as_dirty();
-
-}
-
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
index d2eeda4f64..f89914e445 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
@@ -52,7 +52,6 @@ enum class SLAGizmoEventType : unsigned char {
class CommonGizmosDataBase;
-class AssembleViewDataBase;
namespace CommonGizmosDataObjects {
class SelectionInfo;
class InstancesHider;
@@ -62,11 +61,6 @@ namespace CommonGizmosDataObjects {
class SupportsClipper;
}
-namespace AssembleViewDataObjects {
- class ModelObjectsInfo;
- class ModelObjectsClipper;
-}
-
// Some of the gizmos use the same data that need to be updated ocassionally.
// It is also desirable that the data are not recalculated when the gizmos
// are just switched, but on the other hand, they should be released when
@@ -245,7 +239,7 @@ public:
void set_position_to_init_layer();
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
void render_cut(const std::vector* ignore_idxs = nullptr) const;
- void set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal=false);
+ void set_position_by_ratio(double pos, const Vec3d& normal);
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
@@ -272,121 +266,6 @@ private:
} // namespace CommonGizmosDataObjects
-enum class AssembleViewDataID {
- None = 0,
- ModelObjectsInfo = 1 << 0,
- ModelObjectsClipper = 1 << 4,
-};
-
-class AssembleViewDataPool {
-public:
- AssembleViewDataPool(GLCanvas3D* canvas);
-
- // Update all resources and release what is not used.
- // Accepts a bitmask of currently required resources.
- void update(AssembleViewDataID required);
-
- // Getters for the data that need to be accessed from the gizmos directly.
- AssembleViewDataObjects::ModelObjectsInfo* model_objects_info() const;
- AssembleViewDataObjects::ModelObjectsClipper* model_objects_clipper() const;
-
- GLCanvas3D* get_canvas() const { return m_canvas; }
-
-private:
- std::map> m_data;
- GLCanvas3D* m_canvas;
-
-#ifndef NDEBUG
- bool check_dependencies(AssembleViewDataID required) const;
-#endif
-};
-
-// Base class for a wrapper object managing a single resource.
-// Each of the enum values above (safe None) will have an object of this kind.
-class AssembleViewDataBase {
-public:
- // Pass a backpointer to the pool, so the individual
- // objects can communicate with one another.
- explicit AssembleViewDataBase(AssembleViewDataPool* cgdp)
- : m_common{ cgdp } {}
- virtual ~AssembleViewDataBase() {}
-
- // Update the resource.
- void update() { on_update(); m_is_valid = true; }
-
- // Release any data that are stored internally.
- void release() { on_release(); m_is_valid = false; }
-
- // Returns whether the resource is currently maintained.
- bool is_valid() const { return m_is_valid; }
-
-#ifndef NDEBUG
- // Return a bitmask of all resources that this one relies on.
- // The dependent resource must have higher ID than the one
- // it depends on.
- virtual AssembleViewDataID get_dependencies() const { return AssembleViewDataID::None; }
-#endif // NDEBUG
-
-protected:
- virtual void on_release() = 0;
- virtual void on_update() = 0;
- AssembleViewDataPool* get_pool() const { return m_common; }
-
-
-private:
- bool m_is_valid = false;
- AssembleViewDataPool* m_common = nullptr;
-};
-
-namespace AssembleViewDataObjects
-{
-class ModelObjectsInfo : public AssembleViewDataBase
-{
-public:
- explicit ModelObjectsInfo(AssembleViewDataPool* cgdp)
- : AssembleViewDataBase(cgdp) {}
-
- ModelObjectPtrs model_objects() const { return m_model_objects; }
- //int get_active_instance() const;
- float get_sla_shift() const { return m_z_shift; }
-
-protected:
- void on_update() override;
- void on_release() override;
-
-private:
- ModelObjectPtrs m_model_objects;
- float m_z_shift = 0.f;
-};
-
-class ModelObjectsClipper : public AssembleViewDataBase
-{
-public:
- explicit ModelObjectsClipper(AssembleViewDataPool* cgdp)
- : AssembleViewDataBase(cgdp) {}
-#ifndef NDEBUG
- AssembleViewDataID get_dependencies() const override { return AssembleViewDataID::ModelObjectsInfo; }
-#endif // NDEBUG
-
- void set_position(double pos, bool keep_normal);
- double get_position() const { return m_clp_ratio; }
- ClippingPlane* get_clipping_plane() const { return m_clp.get(); }
- void render_cut() const;
-
-
-protected:
- void on_update() override;
- void on_release() override;
-
-private:
- std::vector m_old_meshes;
- std::vector> m_clippers;
- std::unique_ptr m_clp;
- double m_clp_ratio = 0.;
- double m_active_inst_bb_radius = 0.;
-};
-}
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index f9d88b67b8..3eaaf814b6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -278,8 +278,6 @@ bool GLGizmosManager::init()
//m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
- if(!m_assemble_view_data)
- m_assemble_view_data.reset(new AssembleViewDataPool(&m_parent));
for (auto& gizmo : m_gizmos) {
if (! gizmo->init()) {
@@ -374,7 +372,17 @@ bool GLGizmosManager::init_icon_textures()
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_DARK_HOVER, texture_id));
else
return false;
-
+
+ for (const auto& [icon, name] : std::initializer_list>{
+ { IC_CANVAS_SECTION, "canvas_section" }, { IC_CANVAS_SECTION_HOVER, "canvas_section_hover" },
+ { IC_CANVAS_SECTION_DARK, "canvas_section_dark" }, { IC_CANVAS_SECTION_DARK_HOVER, "canvas_section_dark_hover" },
+ { IC_CANVAS_SECTION_ACTIVE, "canvas_section_active" }, { IC_CANVAS_SECTION_ACTIVE_HOVER, "canvas_section_active_hover" },
+ { IC_CANVAS_SECTION_ACTIVE_DARK, "canvas_section_active_dark" }, { IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, "canvas_section_active_dark_hover" } }) {
+ if (!IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/" + name + ".svg", 72, 72, texture_id))
+ return false;
+ icon_list.insert(std::make_pair((int) icon, texture_id));
+ }
+
return true;
}
@@ -476,14 +484,17 @@ void GLGizmosManager::set_hover_id(int id)
m_gizmos[m_current]->set_hover_id(id);
}
-void GLGizmosManager::update_assemble_view_data()
+void GLGizmosManager::update_section_view()
{
- if (m_assemble_view_data) {
- if (!wxGetApp().plater()->get_assmeble_canvas3D()->get_wxglcanvas()->IsShown())
- m_assemble_view_data->update(AssembleViewDataID(0));
- else
- m_assemble_view_data->update(AssembleViewDataID((int)AssembleViewDataID::ModelObjectsInfo | (int)AssembleViewDataID::ModelObjectsClipper));
- }
+ if (m_current != FdmSupports && m_current != Seam && m_current != MmSegmentation && m_current != FuzzySkin && m_current != BrimEars)
+ return;
+
+ CommonGizmosDataObjects::ObjectClipper* clipper = m_common_gizmos_data ? m_common_gizmos_data->object_clipper() : nullptr;
+ if (clipper == nullptr)
+ return;
+
+ // Brim ears always cut horizontally.
+ clipper->set_position_by_ratio(m_parent.get_section_view_ratio(), m_current == BrimEars ? Vec3d::UnitZ() : m_parent.get_section_view_normal());
}
void GLGizmosManager::update_data()
@@ -496,6 +507,7 @@ void GLGizmosManager::update_data()
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
+ update_section_view();
if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing);
// Orca: hack: Fix issue that flatten gizmo faces not updated after reload from disk
@@ -609,11 +621,11 @@ bool GLGizmosManager::is_allow_select_all() {
return false;
}
-ClippingPlane GLGizmosManager::get_clipping_plane() const
+std::optional GLGizmosManager::get_clipping_plane() const
{
- if (! m_common_gizmos_data
- || ! m_common_gizmos_data->object_clipper()
- || m_common_gizmos_data->object_clipper()->get_position() == 0.)
+ if (! m_common_gizmos_data || ! m_common_gizmos_data->object_clipper())
+ return std::nullopt;
+ else if (m_common_gizmos_data->object_clipper()->get_position() == 0.)
return ClippingPlane::ClipsNothing();
else {
const ClippingPlane& clp = *m_common_gizmos_data->object_clipper()->get_clipping_plane();
@@ -621,18 +633,6 @@ ClippingPlane GLGizmosManager::get_clipping_plane() const
}
}
-ClippingPlane GLGizmosManager::get_assemble_view_clipping_plane() const
-{
- if (!m_assemble_view_data
- || !m_assemble_view_data->model_objects_clipper()
- || m_assemble_view_data->model_objects_clipper()->get_position() == 0.)
- return ClippingPlane::ClipsNothing();
- else {
- const ClippingPlane& clp = *m_assemble_view_data->model_objects_clipper()->get_clipping_plane();
- return ClippingPlane(-clp.get_normal(), clp.get_data()[3]);
- }
-}
-
bool GLGizmosManager::wants_reslice_supports_on_undo() const
{
return false;
@@ -665,12 +665,6 @@ void GLGizmosManager::render_painter_gizmo()
gizmo->render_painter_gizmo();
}
-void GLGizmosManager::render_painter_assemble_view() const
-{
- if (m_assemble_view_data && m_assemble_view_data->model_objects_clipper())
- m_assemble_view_data->model_objects_clipper()->render_cut();
-}
-
// The icon bar, drawn with GL.
void GLGizmosManager::render_overlay()
{
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 63207f741b..855e26c102 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -22,6 +22,7 @@
#include
#include
#include