Merge upstream, including section view and a faster preview

Five commits: a section view for the 3D canvas, a faster G-code preview, user
preset values kept on extruder variants they don't list, debug build CMake
fixes, and a 30 minute timeout on macOS notarization.

Two conflicts, both in GLCanvas3D. The header's were neighboring
declarations, kept from both sides. In the transparent pass, the section view
removed the assemble view's painter block that followed our ghost render;
the ghost render stays where it was, at the end of that pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-10-02 15:14:03 -04:00
co-authored by Claude Opus 5.5
63 changed files with 2509 additions and 927 deletions
+1
View File
@@ -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 }}
+3 -1
View File
@@ -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})
+1 -5
View File
@@ -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)
+93
View File
@@ -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.
+105
View File
@@ -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.
+1
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#fafafa;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 1.2 KiB

+5 -1
View File
@@ -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;
+6 -2
View File
@@ -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);
+7 -3
View File
@@ -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);
+4
View File
@@ -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;
+5 -1
View File
@@ -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;
+6 -2
View File
@@ -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);
+7 -3
View File
@@ -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);
+4
View File
@@ -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;
+6 -2
View File
@@ -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);
+3
View File
@@ -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"
+59 -27
View File
@@ -8,6 +8,7 @@
#include "format.hpp"
#include "GCode/Thumbnails.hpp"
#include <numeric>
#include <set>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
@@ -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<std::string>& variant_list, const std::vector<int>& 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<int> map_variant_indices(const std::vector<std::string>& variants, const std::vector<int>& ids,
const std::vector<std::string>& from_variants, const std::vector<int>& from_ids)
{
const size_t count = variants.empty() ? ids.size() : variants.size();
std::vector<int> 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<std::string>& variant_list, const std::vector<int>& 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<NozzleVolumeType> 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<ConfigOptionStrings>("filament_extruder_variant");
const std::vector<std::string> variant_list = variants && variants->size() == self_index->size() ? variants->values : std::vector<std::string>();
std::vector<int> filament_ids(filament_count);
std::iota(filament_ids.begin(), filament_ids.end(), 1);
const std::vector<int> from_filaments = map_variant_indices(variant_list, self_index->values, {}, filament_ids);
const std::vector<int> from_single = map_variant_indices(variant_list, self_index->values, {}, {});
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
const std::vector<int> &variant_index = opt->size() == size_t(filament_count) ? from_filaments : from_single;
std::unique_ptr<ConfigOption> 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<int>() : std::vector<int>{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();
+13 -2
View File
@@ -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<std::string>& variant_list, const std::vector<int>& 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<int> map_variant_indices(const std::vector<std::string>& variants, const std::vector<int>& ids,
const std::vector<std::string>& from_variants, const std::vector<int>& 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<std::string>& variant_list, const std::vector<int>& variant_ids_1based);
static std::set<NozzleVolumeType> get_valid_nozzle_volume_type() {
+2
View File
@@ -37,6 +37,8 @@ static constexpr float MAX_WIPES_RADIUS_MM = 1.0f;
// Used for positions, displacements and so on.
//
using Vec3 = std::array<float, 3>;
// ORCA: the per vertex data uploaded as GL_RGBA32F, see extract_pos_and_or_hwa().
using Vec4 = std::array<float, 4>;
//
// 4x4 square matrix with elements in column-major order:
+8
View File
@@ -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<float, 4>& 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);
//
// ************************************************************************
+146 -27
View File
@@ -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";
+162 -19
View File
@@ -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";
+10
View File
@@ -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<float, 4>& plane)
{
m_impl->set_clipping_plane(plane);
}
EViewType Viewer::get_view_type() const
{
return m_impl->get_view_type();
+162 -128
View File
@@ -349,7 +349,7 @@ void ViewerImpl::TextureData::init(size_t vertices_count)
m_tex_ids = std::vector<TexIds>(m_count);
}
void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& positions)
void ViewerImpl::TextureData::set_positions(const std::vector<Vec4>& positions)
{
if (m_count == 0)
return;
@@ -383,17 +383,17 @@ void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& 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<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &positions[offset]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(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<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &positions[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &positions[offset + w * (h - 1)]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGBA, GL_FLOAT, &positions[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(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<Vec3>& positions)
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec3>& heights_widths_angles)
void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec4>& heights_widths_angles)
{
if (m_count == 0)
return;
@@ -436,17 +436,17 @@ void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec3>&
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<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(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<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGBA, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(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<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().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<float, 4>;
static void extract_pos_and_or_hwa(const std::vector<PathVertex>& vertices, float travels_radius, float wipes_radius, BitSet<>& valid_lines_bitset,
std::vector<Vec4>* positions = nullptr, std::vector<Vec4>* heights_widths_angles = nullptr, bool update_bitset = false) {
@@ -959,8 +990,8 @@ static void extract_pos_and_or_hwa(const std::vector<PathVertex>& 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<float>(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<float>(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<int>(color[0] * brightness);
const int g = static_cast<int>(color[1] * brightness);
const int b = static_cast<int>(color[2] * brightness);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(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<float>& 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<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
// ORCA: layers kept at full brightness by the dimming above are excluded from the greying below too
const bool inspected_layer = dim_previous_layers &&
((inspecting_top_layer && v.layer_id == layers_range[1]) ||
(inspecting_bottom_layer && v.layer_id == layers_range[0]));
if (dim_previous_layers && !inspected_layer && !keep_spiral_seam)
colors[i] = encode_color_dimmed(get_vertex_color(v), m_settings.dim_previous_layers_brightness);
else if (!inspected_layer && color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color(DUMMY_COLOR);
else
colors[i] = m_vertices_colors[i];
}
#ifdef ENABLE_OPENGL_ES
if (!colors.empty())
// update gpu buffer for colors
m_texture_data.set_colors(colors);
#else
m_colors_tex_size = colors.size() * sizeof(float);
// update gpu buffer for colors
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, colors.size() * sizeof(float), colors.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
// Without dimming, the layers below the top one go dark grey while the moves slider is short of
// the end. Neither touches the inspected layers, nor the first vertex of a spiral vase layer.
glsafe(glUniform2i(uni.lit_layers, dim_previous_layers && inspecting_bottom_layer ? static_cast<int>(layers_range[0]) : -1,
dim_previous_layers && inspecting_top_layer ? static_cast<int>(layers_range[1]) : -1));
glsafe(glUniform1i(uni.grey_below_layer, color_top_layer_only ? static_cast<int>(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<int>(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<ETimeMode> 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<float>& 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<Vec3> heights_widths_angles;
std::vector<Vec4> 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<int>(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<int>(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<GLsizei>(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<int, 4> 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));
+52 -17
View File
@@ -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<float, 4>& 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<uint32_t> 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<float> 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<Vec3>& positions);
void set_heights_widths_angles(const std::vector<Vec3>& heights_widths_angles);
void set_positions(const std::vector<Vec4>& positions);
void set_heights_widths_angles(const std::vector<Vec4>& heights_widths_angles);
void set_colors(const std::vector<float>& colors);
void set_enabled_segments(const std::vector<uint32_t>& enabled_segments);
void set_enabled_options(const std::vector<uint32_t>& 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<float, 4> 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);
+4
View File
@@ -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
+10 -1
View File
@@ -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;
}
);
}
+153
View File
@@ -0,0 +1,153 @@
#include "libslic3r/libslic3r.h"
#include "FrameProfiler.hpp"
#include "3DScene.hpp"
#include <glad/gl.h>
#include <algorithm>
#include <cstring>
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<double, std::milli>(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<GLuint64, 2 * MAX_SECTIONS> 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<Section> 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(), [&section](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::Section> FrameProfiler::finish_averaging(bool wait)
{
if (wait && glQueryCounter != nullptr)
collect(true);
std::vector<Section> 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
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <array>
#include <chrono>
#include <cstddef>
#include <vector>
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<Section>& 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<Section> 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<unsigned int, 2 * MAX_SECTIONS> queries{};
std::array<const char*, MAX_SECTIONS> names{};
std::array<double, MAX_SECTIONS> cpu_ms{};
size_t count{ 0 };
bool pending{ false };
bool averaged{ false };
};
void collect(bool wait);
std::array<Frame, FRAMES_IN_FLIGHT> m_frames;
Frame* m_recording{ nullptr };
size_t m_next{ 0 };
std::chrono::steady_clock::time_point m_last_mark;
std::vector<Section> m_sections;
bool m_averaging{ false };
std::vector<Section> m_sums;
size_t m_averaged_frames{ 0 };
};
} // namespace GUI
} // namespace Slic3r
+36
View File
@@ -83,6 +83,7 @@
#include <imgui/imgui_internal.h>
#include <glad/gl.h>
#include <boost/functional/hash.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/cstdio.hpp>
@@ -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<Vec3f>(direction.cast<float>())));
}
void GCodeViewer::set_tone(float exposure, float saturation)
{
m_viewer.set_tone(exposure, saturation);
}
void GCodeViewer::set_clipping_plane(const ClippingPlane& plane)
{
// Flipped to match ClippingPlane::distance().
const Vec3f normal = -plane.get_normal().cast<float>();
m_clipping_plane = plane.is_active() ?
std::array<float, 4>{ normal.x(), normal.y(), normal.z(), float(plane.get_offset()) } :
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f };
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())
+9
View File
@@ -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<float, 4> 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<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
const std::array<size_t,2> &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);
+454 -143
View File
@@ -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<size_t> 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<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
m_frame_profiler.mark("ssao");
}
if (_is_fxaa_enabled())
if (_is_fxaa_enabled()) {
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(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<Vec3d> 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<FrameProfiler::Section>& 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<GLuint>(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<GLuint>(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<GLuint>(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<GLuint>(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<float>(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<Vec3d> 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<const GLVolume*, MeshClipper> caps;
for (const GLVolume* volume : m_volumes.volumes) {
if (!volume->is_active || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0 ||
volume->object_idx() >= int(m_model->objects.size()))
continue;
const ModelObject* object = m_model->objects[volume->object_idx()];
if (volume->volume_idx() >= int(object->volumes.size()))
continue;
// Skip volumes the plane misses.
const BoundingBoxf3& box = volume->transformed_bounding_box();
if (std::abs(plane.distance(box.center())) > 0.5 * box.size().dot(plane.get_normal().cwiseAbs()))
continue;
auto node = m_section_view_caps.extract(volume);
MeshClipper& clipper = node ? caps.insert(std::move(node)).position->second : caps[volume];
clipper.set_mesh(object->volumes[volume->volume_idx()]->mesh().its);
clipper.set_plane(plane);
clipper.set_transformation(Geometry::Transformation(volume->world_matrix()));
// No cut face below the plate.
if (m_canvas_type != CanvasAssembleView)
clipper.set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
clipper.render_cut({ 0.25f, 0.25f, 0.25f, 1.0f });
}
m_section_view_caps = std::move(caps);
}
bool GLCanvas3D::_is_xray_view_active() const
{
if (m_canvas_type == ECanvasType::CanvasPreview || !wxGetApp().plater()->is_show_xray())
@@ -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<std::pair<wxString, wxString>> tips = {
{ alt + _L("Mouse wheel"), _L("Move section plane") },
{ _L("Mouse wheel on icon"), _L("Move section plane") },
{ _L("Right click on icon"), _L("Toggle section view") },
{ _L("Middle click on icon"), _L("Set viewing angle") },
};
// render_tooltip_button() puts the tips below y plus the panel height; they go above the panel.
const ImGuiStyle& style = ImGui::GetStyle();
const float tips_h = tips.size() * (ImGui::GetTextLineHeight() + style.ItemSpacing.y) - style.ItemSpacing.y + 2.f * style.WindowPadding.y;
GLGizmoUtils::render_tooltip_button(&imgui, *this, tips, ImGui::GetWindowPos().x,
ImGui::GetWindowPos().y - style.ItemSpacing.y - tips_h - ImGui::GetContentRegionMax().y - ImGui::GetFrameHeight());
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
imgui.text(_L("Section view"));
float ratio = float(get_section_view_ratio());
ImGui::SameLine();
ImGui::SetNextItemWidth(imgui.scaled(7.f));
bool changed = imgui.bbl_slider_float_style("##section_view", &ratio, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine();
ImGui::SetNextItemWidth(1.5f * imgui.get_slider_icon_size().x);
changed |= ImGui::BBLDragFloat("##section_view_input", &ratio, 0.05f, 0.0f, 0.0f, "%.2f");
if (changed)
set_section_view_ratio(ratio);
ImGui::SameLine();
if (imgui.button(_L("Set viewing angle")))
align_section_view_to_camera();
imgui.disabled_begin(!is_section_view_active());
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f);
if (imgui.button(wxString(ImGui::RevertBtn) + "##section_view_reset", _L("Reset")))
set_section_view_ratio(0.);
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
imgui.disabled_end();
imgui.end();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
ImGuiWrapper::pop_toolbar_style();
}
void GLCanvas3D::_render_separator_toolbar_right() const
@@ -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<ClippingPlane> gizmo_plane = m_gizmos.get_clipping_plane();
return gizmo_plane.has_value() ? *gizmo_plane : _get_section_view_plane().inverted_normal();
}
ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const
{
// Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and
// invert the result into the convention expected by SceneRaycaster.
GLGizmoBase* current_gizmo = m_gizmos.get_current();
return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? _get_volumes_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
}
+45
View File
@@ -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<ClippingPlane, 2> m_clipping_planes;
ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
struct SectionView
{
explicit SectionView(const GLCanvas3D* owner) : owner(owner) {}
double ratio{ 0. }; // 0 = off
double last_ratio{ 0. }; // before it was switched off
Vec3d normal{ Vec3d::Zero() }; // zero until first aimed
bool panel_open{ false };
const GLCanvas3D* owner; // whose objects place the plane
};
std::shared_ptr<SectionView> m_section_view{ std::make_shared<SectionView>(this) };
std::map<const GLVolume*, MeshClipper> m_section_view_caps;
// Its release would open the hidden window menu on Windows.
bool m_alt_wheel_used{ false };
std::array<SlaCap, 2> m_sla_caps;
std::string m_sidebar_field;
// when true renders an extra frame by not resetting m_dirty to false
@@ -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<std::chrono::steady_clock> 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<float, 4> 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<ColorRGBA, 2>& colors) { m_volumes.set_color_clip_plane_colors(colors); }
bool is_section_view_active() const { return m_section_view->ratio > 0.; }
double get_section_view_ratio() const { return m_section_view->ratio; }
const Vec3d& get_section_view_normal() const { return m_section_view->normal; }
void set_section_view_ratio(double ratio);
void toggle_section_view();
void align_section_view_to_camera();
void share_section_view(const GLCanvas3D& owner) { m_section_view = owner.m_section_view; }
void toggle_world_axes_visibility(bool force_show = false);
void refresh_camera_scene_box();
void set_color_by(const std::string& value);
@@ -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<Vec3d> _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;
+2 -2
View File
@@ -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<std::string> previous_opengl_render_settings;
for (const char* key : opengl_render_setting_keys)
+2 -40
View File
@@ -85,7 +85,6 @@ bool GLGizmoBrimEars::on_init()
m_shortcut = Shortcut::GizmoBrimEars;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
m_desc["brim_ear_radius"] = _L("Brim ear radius");
m_desc["max_angle"] = _L("Max angle");
@@ -96,13 +95,11 @@ bool GLGizmoBrimEars::on_init()
m_desc["create"] = _L("Create");
m_desc["auto_generate"] = _L("Auto-generate");
m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius");
m_desc["section_view"] = _L("Section view");
m_shortcuts = {
{_L("Left mouse button"), _L("Add or Select")},
{_L("Right mouse button"), _L("Remove")},
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
{alt + _L("Mouse wheel"), m_desc["section_view"]},
};
return true;
@@ -551,26 +548,6 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
apply_radius_change();
}
if (action == SLAGizmoEventType::MouseWheelUp && alt_down) {
double pos = m_c->object_clipper()->get_position();
pos = std::min(1., pos + 0.01);
m_c->object_clipper()->set_position_by_ratio(pos, false, true);
return true;
}
if (action == SLAGizmoEventType::MouseWheelDown && alt_down) {
double pos = m_c->object_clipper()->get_position();
pos = std::max(0., pos - 0.01);
m_c->object_clipper()->set_position_by_ratio(pos, false, true);
return true;
}
// reset clipper position
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position_by_ratio(-1., false);
return true;
}
return false;
}
@@ -692,8 +669,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
float space_size = m_imgui->get_style_scaling() * 8;
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
m_desc["create"], m_desc["remove"]};
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["create"],
m_desc["remove"]};
float widest_text = m_imgui->find_widest_text(text_list);
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
float input_text_size = m_imgui->scaled(10.0f);
@@ -782,21 +759,6 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
}
}
m_imgui->disabled_end();
ImGui::Separator();
ImGui::AlignTextToFramePadding();
float clp_dist = float(m_c->object_clipper()->get_position());
m_imgui->text(m_desc["section_view"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##section_view", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) {
m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true);
}
ImGui::Separator();
+4 -35
View File
@@ -112,36 +112,30 @@ bool GLGizmoFdmSupports::on_init()
m_desc["highlight_by_angle"] = _L("Highlight overhangs");
m_desc["tool_type"] = _L("Tool type");
m_desc["gap_fill"] = _L("Gap fill");
m_desc["reset_direction"] = _L("Reset direction");
m_desc["clipping_of_view"] = _L("Section view");
m_desc["cursor_size"] = _L("Brush size");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["gap_area"] = _L("Gap area");
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
std::pair<wxString, wxString> enforce_shortcut = {_L("Left mouse button"), _L("Enforce supports")};
std::pair<wxString, wxString> block_shortcut = {_L("Right mouse button"), _L("Block supports")};
std::pair<wxString, wxString> remove_shortcut = {shift + _L("Left mouse button"), _L("Erase")};
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
m_shortcuts_brush = {
enforce_shortcut,
block_shortcut,
remove_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
clipping_shortcut
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
m_shortcuts_bucket_fill = {
enforce_shortcut,
block_shortcut,
remove_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
clipping_shortcut
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}
};
m_shortcuts_gap_fill = {
@@ -237,11 +231,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float gap_fill_slider_left = m_imgui->calc_text_size(m_desc.at("gap_fill")).x + m_imgui->scaled(1.5f);
const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f);
const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2;
const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f);
const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f);
@@ -254,14 +246,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 5>{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 4>{"enforce", "block", "remove", "cursor_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(reset_button_slider_left, std::max(std::max(cursor_slider_left, clipping_slider_left), std::max(highlight_slider_left, gap_fill_slider_left)))));
const float sliders_left_width = std::max(gap_area_txt_width, std::max(smart_fill_angle_txt_width, std::max(cursor_slider_left, std::max(highlight_slider_left, gap_fill_slider_left))));
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
@@ -458,29 +450,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##angle_threshold_deg_input", &m_highlight_by_angle_threshold_deg, 0.05f, 0.0f, 0.0f, "%.2f");
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
}
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this]() {
m_c->object_clipper()->set_position_by_ratio(-1., false);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator();
render_tooltip_button(x, y);
+6 -41
View File
@@ -51,10 +51,8 @@ bool GLGizmoFuzzySkin::on_init()
m_shortcut = Shortcut::GizmoFuzzySkin;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["reset_direction"] = _L("Reset direction");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
@@ -62,7 +60,6 @@ bool GLGizmoFuzzySkin::on_init()
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_smart_fill"] = _L("Smart fill");
m_desc["clipping_of_view"] = _L("Section view");
m_desc["cursor_size"] = _L("Brush size");
m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin");
m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin");
@@ -70,26 +67,22 @@ bool GLGizmoFuzzySkin::on_init()
std::pair<wxString, wxString> add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]};
std::pair<wxString, wxString> remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]};
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
m_shortcuts_brush = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
clipping_shortcut
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
m_shortcuts_triangle = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
clipping_shortcut
remove_fuzzy_skin_shortcut
};
m_shortcuts_smart_fill = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
clipping_shortcut
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}
};
return true;
@@ -162,8 +155,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
@@ -187,9 +178,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float circle_max_width = std::max(clipping_slider_left, cursor_slider_left);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
const float sliders_left_width = std::max(smart_fill_slider_left, cursor_slider_left);
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const float empty_button_width = m_imgui->calc_button_size("").x;
@@ -259,10 +248,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(circle_max_width);
ImGui::SameLine(cursor_slider_left);
ImGui::PushItemWidth(sliders_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + circle_max_width);
ImGui::SameLine(drag_left_width + cursor_slider_left);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::TriangleButtonIcon) {
@@ -291,30 +280,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim
ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f");
}
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
}
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position_by_ratio(-1., false);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); }
ImGui::Separator();
render_tooltip_button(x, y);
@@ -118,8 +118,6 @@ bool GLGizmoMmuSegmentation::on_init()
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["clipping_of_view"] = _L("Section view");
m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Brush size");
m_desc["cursor_type"] = _L("Brush shape");
m_desc["paint"] = _L("Paint");
@@ -146,14 +144,12 @@ bool GLGizmoMmuSegmentation::on_init()
std::pair<wxString, wxString> paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
std::pair<wxString, wxString> erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]};
m_shortcuts_brush = {
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
clipping_shortcut,
toggle_wireframe_shortcut
};
@@ -161,7 +157,6 @@ bool GLGizmoMmuSegmentation::on_init()
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
clipping_shortcut,
toggle_wireframe_shortcut
};
@@ -397,8 +392,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
const float edge_detect_slider_left = m_imgui->calc_text_size(m_desc.at("edge_detection")).x + m_imgui->scaled(1.f);
@@ -413,18 +406,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 6>{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 5>{"paint", "erase", "cursor_size", "smart_fill_angle", "height_range"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float circle_max_width = std::max(clipping_slider_left,cursor_slider_left);
const float height_max_width = std::max(clipping_slider_left,height_range_slider_left);
const float sliders_left_width = std::max(smart_fill_slider_left,
std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, std::max(height_range_slider_left,
clipping_slider_left))))) + space_size;
std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(gap_area_slider_left, height_range_slider_left)))) + space_size;
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const int max_filament_items_per_line = 8;
@@ -658,28 +648,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
}
}
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
} else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); });
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) {
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
}
ImGui::Separator();
render_tooltip_button(x, y);
@@ -750,20 +750,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return true;
}
}
else if (alt_down) {
// BBS
double pos = m_c->object_clipper()->get_position();
pos = action == SLAGizmoEventType::MouseWheelDown
? std::max(0., pos - 0.01)
: std::min(1., pos + 0.01);
m_c->object_clipper()->set_position_by_ratio(pos, true);
return true;
}
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position_by_ratio(-1., false);
return true;
}
if (action == SLAGizmoEventType::LeftDown
+3 -32
View File
@@ -46,11 +46,8 @@ bool GLGizmoSeam::on_init()
m_shortcut = Shortcut::GizmoSeam;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["clipping_of_view"] = _L("Section view");
m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Brush size");
m_desc["tool_type"] = _L("Tool type");
m_desc["enforce"] = _L("Enforce seam");
@@ -64,8 +61,7 @@ bool GLGizmoSeam::on_init()
{_L("Left mouse button"), m_desc["enforce"]},
{_L("Right mouse button"), m_desc["block"]},
{shift + _L("Left mouse button"), m_desc["remove"]},
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
{alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}
};
return true;
@@ -155,20 +151,17 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
m_imgui->calc_text_size(m_desc.at("reset_direction")).x + ImGui::GetStyle().FramePadding.x * 2)
+ m_imgui->scaled(1.5f);
const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float empty_button_width = m_imgui->calc_button_size("").x;
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 6>{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) {
for (const auto &t : std::array<std::string, 4>{"enforce", "block", "remove", "cursor_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left);
const float sliders_left_width = cursor_size_slider_left;
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
const float sliders_width = m_imgui->scaled(7.0f);
@@ -242,28 +235,6 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only);
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
}
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position_by_ratio(-1., false);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); }
ImGui::Separator();
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y);
+8 -180
View File
@@ -380,22 +380,19 @@ std::vector<Vec3d> ObjectClipper::point_per_contour() const
}
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal)
void ObjectClipper::set_position_by_ratio(double pos, const Vec3d& normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
Vec3d normal;
if(vertical_normal) {
normal = {0, 0, 1};
}else {
//Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
//if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
// camera_dir(2) = 0;
normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward();
if (pos == 0. || mo == nullptr || active_inst < 0) {
// No plane, so the raycasts are not clipped.
m_clp_ratio = 0.;
m_clp.reset();
get_pool()->get_canvas()->set_as_dirty();
return;
}
Vec3d center;
if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
@@ -410,9 +407,6 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool ver
float dist = normal.dot(center);
if (pos < 0.)
pos = m_clp_ratio;
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2 * m_active_inst_bb_radius)));
@@ -445,171 +439,5 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou
}
using namespace AssembleViewDataObjects;
AssembleViewDataPool::AssembleViewDataPool(GLCanvas3D* canvas)
: m_canvas(canvas)
{
using c = AssembleViewDataID;
m_data[c::ModelObjectsInfo].reset(new ModelObjectsInfo(this));
m_data[c::ModelObjectsClipper].reset(new ModelObjectsClipper(this));
}
void AssembleViewDataPool::update(AssembleViewDataID required)
{
assert(check_dependencies(required));
for (auto& [id, data] : m_data) {
if (int(required) & int(AssembleViewDataID(id)))
data->update();
else
if (data->is_valid())
data->release();
}
}
ModelObjectsInfo* AssembleViewDataPool::model_objects_info() const
{
ModelObjectsInfo* sel_info = dynamic_cast<ModelObjectsInfo*>(m_data.at(AssembleViewDataID::ModelObjectsInfo).get());
assert(sel_info);
return sel_info->is_valid() ? sel_info : nullptr;
}
ModelObjectsClipper* AssembleViewDataPool::model_objects_clipper() const
{
ModelObjectsClipper* oc = dynamic_cast<ModelObjectsClipper*>(m_data.at(AssembleViewDataID::ModelObjectsClipper).get());
// ObjectClipper is used from outside the gizmos to report current clipping plane.
// This function can be called when oc is nullptr.
return (oc && oc->is_valid()) ? oc : nullptr;
}
#ifndef NDEBUG
// Check the required resources one by one and return true if all
// dependencies are met.
bool AssembleViewDataPool::check_dependencies(AssembleViewDataID required) const
{
// This should iterate over currently required data. Each of them should
// be asked about its dependencies and it must check that all dependencies
// are also in required and before the current one.
for (auto& [id, data] : m_data) {
// in case we don't use this, the deps are irrelevant
if (!(int(required) & int(AssembleViewDataID(id))))
continue;
AssembleViewDataID deps = data->get_dependencies();
assert(int(deps) == (int(deps) & int(required)));
}
return true;
}
#endif // NDEBUG
void ModelObjectsInfo::on_update()
{
if (!get_pool()->get_canvas()->get_model()->objects.empty()) {
m_model_objects = get_pool()->get_canvas()->get_model()->objects;
}
else {
m_model_objects.clear();
}
}
void ModelObjectsInfo::on_release()
{
m_model_objects.clear();
}
//int ModelObjectsInfo::get_active_instance() const
//{
// const Selection& selection = get_pool()->get_canvas()->get_selection();
// return selection.get_instance_idx();
//}
void ModelObjectsClipper::on_update()
{
const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects();
if (model_objects.empty())
return;
// which mesh should be cut?
std::vector<const TriangleMesh*> meshes;
if (meshes.empty())
for (auto mo : model_objects) {
for (const ModelVolume* mv : mo->volumes)
meshes.push_back(&mv->mesh());
}
if (meshes != m_old_meshes) {
m_clippers.clear();
for (const TriangleMesh* mesh : meshes) {
m_clippers.emplace_back(new MeshClipper);
m_clippers.back()->set_mesh(mesh->its);
}
m_old_meshes = meshes;
m_active_inst_bb_radius = get_pool()->get_canvas()->volumes_bounding_box().radius();
}
}
void ModelObjectsClipper::on_release()
{
m_clippers.clear();
m_old_meshes.clear();
m_clp.reset();
m_clp_ratio = 0.;
}
void ModelObjectsClipper::render_cut() const
{
if (m_clp_ratio == 0.)
return;
const ModelObjectPtrs model_objects = get_pool()->model_objects_info()->model_objects();
size_t clipper_id = 0;
for (const ModelObject* mo : model_objects) {
Geometry::Transformation assemble_objects_trafo = mo->instances[0]->get_assemble_transformation();
auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly();
for (const ModelVolume* mv : mo->volumes) {
Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = assemble_objects_trafo * vol_trafo;
trafo.set_offset(trafo.get_offset() + vol_trafo.get_offset() * (GLVolume::explosion_ratio - 1.0) + offset_to_assembly * (GLVolume::explosion_ratio - 1.0));
auto& clipper = m_clippers[clipper_id];
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
// BBS
clipper->render_cut({0.25f, 0.25f, 0.25f, 1.0f});
++clipper_id;
}
}
}
void ModelObjectsClipper::set_position(double pos, bool keep_normal)
{
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
const Vec3d& center = get_pool()->get_canvas()->volumes_bounding_box().center();
float dist = normal.dot(center);
if (pos < 0.)
pos = m_clp_ratio;
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius * GLVolume::explosion_ratio) - m_clp_ratio * 2 * m_active_inst_bb_radius * GLVolume::explosion_ratio)));
get_pool()->get_canvas()->set_as_dirty();
}
} // namespace GUI
} // namespace Slic3r
+1 -122
View File
@@ -52,7 +52,6 @@ enum class SLAGizmoEventType : unsigned char {
class CommonGizmosDataBase;
class AssembleViewDataBase;
namespace CommonGizmosDataObjects {
class SelectionInfo;
class InstancesHider;
@@ -62,11 +61,6 @@ namespace CommonGizmosDataObjects {
class SupportsClipper;
}
namespace AssembleViewDataObjects {
class ModelObjectsInfo;
class ModelObjectsClipper;
}
// Some of the gizmos use the same data that need to be updated ocassionally.
// It is also desirable that the data are not recalculated when the gizmos
// are just switched, but on the other hand, they should be released when
@@ -245,7 +239,7 @@ public:
void set_position_to_init_layer();
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
void render_cut(const std::vector<size_t>* ignore_idxs = nullptr) const;
void set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal=false);
void set_position_by_ratio(double pos, const Vec3d& normal);
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
@@ -272,121 +266,6 @@ private:
} // namespace CommonGizmosDataObjects
enum class AssembleViewDataID {
None = 0,
ModelObjectsInfo = 1 << 0,
ModelObjectsClipper = 1 << 4,
};
class AssembleViewDataPool {
public:
AssembleViewDataPool(GLCanvas3D* canvas);
// Update all resources and release what is not used.
// Accepts a bitmask of currently required resources.
void update(AssembleViewDataID required);
// Getters for the data that need to be accessed from the gizmos directly.
AssembleViewDataObjects::ModelObjectsInfo* model_objects_info() const;
AssembleViewDataObjects::ModelObjectsClipper* model_objects_clipper() const;
GLCanvas3D* get_canvas() const { return m_canvas; }
private:
std::map<AssembleViewDataID, std::unique_ptr<AssembleViewDataBase>> m_data;
GLCanvas3D* m_canvas;
#ifndef NDEBUG
bool check_dependencies(AssembleViewDataID required) const;
#endif
};
// Base class for a wrapper object managing a single resource.
// Each of the enum values above (safe None) will have an object of this kind.
class AssembleViewDataBase {
public:
// Pass a backpointer to the pool, so the individual
// objects can communicate with one another.
explicit AssembleViewDataBase(AssembleViewDataPool* cgdp)
: m_common{ cgdp } {}
virtual ~AssembleViewDataBase() {}
// Update the resource.
void update() { on_update(); m_is_valid = true; }
// Release any data that are stored internally.
void release() { on_release(); m_is_valid = false; }
// Returns whether the resource is currently maintained.
bool is_valid() const { return m_is_valid; }
#ifndef NDEBUG
// Return a bitmask of all resources that this one relies on.
// The dependent resource must have higher ID than the one
// it depends on.
virtual AssembleViewDataID get_dependencies() const { return AssembleViewDataID::None; }
#endif // NDEBUG
protected:
virtual void on_release() = 0;
virtual void on_update() = 0;
AssembleViewDataPool* get_pool() const { return m_common; }
private:
bool m_is_valid = false;
AssembleViewDataPool* m_common = nullptr;
};
namespace AssembleViewDataObjects
{
class ModelObjectsInfo : public AssembleViewDataBase
{
public:
explicit ModelObjectsInfo(AssembleViewDataPool* cgdp)
: AssembleViewDataBase(cgdp) {}
ModelObjectPtrs model_objects() const { return m_model_objects; }
//int get_active_instance() const;
float get_sla_shift() const { return m_z_shift; }
protected:
void on_update() override;
void on_release() override;
private:
ModelObjectPtrs m_model_objects;
float m_z_shift = 0.f;
};
class ModelObjectsClipper : public AssembleViewDataBase
{
public:
explicit ModelObjectsClipper(AssembleViewDataPool* cgdp)
: AssembleViewDataBase(cgdp) {}
#ifndef NDEBUG
AssembleViewDataID get_dependencies() const override { return AssembleViewDataID::ModelObjectsInfo; }
#endif // NDEBUG
void set_position(double pos, bool keep_normal);
double get_position() const { return m_clp_ratio; }
ClippingPlane* get_clipping_plane() const { return m_clp.get(); }
void render_cut() const;
protected:
void on_update() override;
void on_release() override;
private:
std::vector<const TriangleMesh*> m_old_meshes;
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
std::unique_ptr<ClippingPlane> m_clp;
double m_clp_ratio = 0.;
double m_active_inst_bb_radius = 0.;
};
}
} // namespace GUI
} // namespace Slic3r
+26 -32
View File
@@ -278,8 +278,6 @@ bool GLGizmosManager::init()
//m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
if(!m_assemble_view_data)
m_assemble_view_data.reset(new AssembleViewDataPool(&m_parent));
for (auto& gizmo : m_gizmos) {
if (! gizmo->init()) {
@@ -374,7 +372,17 @@ bool GLGizmosManager::init_icon_textures()
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_DARK_HOVER, texture_id));
else
return false;
for (const auto& [icon, name] : std::initializer_list<std::pair<MENU_ICON_NAME, const char*>>{
{ IC_CANVAS_SECTION, "canvas_section" }, { IC_CANVAS_SECTION_HOVER, "canvas_section_hover" },
{ IC_CANVAS_SECTION_DARK, "canvas_section_dark" }, { IC_CANVAS_SECTION_DARK_HOVER, "canvas_section_dark_hover" },
{ IC_CANVAS_SECTION_ACTIVE, "canvas_section_active" }, { IC_CANVAS_SECTION_ACTIVE_HOVER, "canvas_section_active_hover" },
{ IC_CANVAS_SECTION_ACTIVE_DARK, "canvas_section_active_dark" }, { IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, "canvas_section_active_dark_hover" } }) {
if (!IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/" + name + ".svg", 72, 72, texture_id))
return false;
icon_list.insert(std::make_pair((int) icon, texture_id));
}
return true;
}
@@ -476,14 +484,17 @@ void GLGizmosManager::set_hover_id(int id)
m_gizmos[m_current]->set_hover_id(id);
}
void GLGizmosManager::update_assemble_view_data()
void GLGizmosManager::update_section_view()
{
if (m_assemble_view_data) {
if (!wxGetApp().plater()->get_assmeble_canvas3D()->get_wxglcanvas()->IsShown())
m_assemble_view_data->update(AssembleViewDataID(0));
else
m_assemble_view_data->update(AssembleViewDataID((int)AssembleViewDataID::ModelObjectsInfo | (int)AssembleViewDataID::ModelObjectsClipper));
}
if (m_current != FdmSupports && m_current != Seam && m_current != MmSegmentation && m_current != FuzzySkin && m_current != BrimEars)
return;
CommonGizmosDataObjects::ObjectClipper* clipper = m_common_gizmos_data ? m_common_gizmos_data->object_clipper() : nullptr;
if (clipper == nullptr)
return;
// Brim ears always cut horizontally.
clipper->set_position_by_ratio(m_parent.get_section_view_ratio(), m_current == BrimEars ? Vec3d::UnitZ() : m_parent.get_section_view_normal());
}
void GLGizmosManager::update_data()
@@ -496,6 +507,7 @@ void GLGizmosManager::update_data()
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
update_section_view();
if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing);
// Orca: hack: Fix issue that flatten gizmo faces not updated after reload from disk
@@ -609,11 +621,11 @@ bool GLGizmosManager::is_allow_select_all() {
return false;
}
ClippingPlane GLGizmosManager::get_clipping_plane() const
std::optional<ClippingPlane> GLGizmosManager::get_clipping_plane() const
{
if (! m_common_gizmos_data
|| ! m_common_gizmos_data->object_clipper()
|| m_common_gizmos_data->object_clipper()->get_position() == 0.)
if (! m_common_gizmos_data || ! m_common_gizmos_data->object_clipper())
return std::nullopt;
else if (m_common_gizmos_data->object_clipper()->get_position() == 0.)
return ClippingPlane::ClipsNothing();
else {
const ClippingPlane& clp = *m_common_gizmos_data->object_clipper()->get_clipping_plane();
@@ -621,18 +633,6 @@ ClippingPlane GLGizmosManager::get_clipping_plane() const
}
}
ClippingPlane GLGizmosManager::get_assemble_view_clipping_plane() const
{
if (!m_assemble_view_data
|| !m_assemble_view_data->model_objects_clipper()
|| m_assemble_view_data->model_objects_clipper()->get_position() == 0.)
return ClippingPlane::ClipsNothing();
else {
const ClippingPlane& clp = *m_assemble_view_data->model_objects_clipper()->get_clipping_plane();
return ClippingPlane(-clp.get_normal(), clp.get_data()[3]);
}
}
bool GLGizmosManager::wants_reslice_supports_on_undo() const
{
return false;
@@ -665,12 +665,6 @@ void GLGizmosManager::render_painter_gizmo()
gizmo->render_painter_gizmo();
}
void GLGizmosManager::render_painter_assemble_view() const
{
if (m_assemble_view_data && m_assemble_view_data->model_objects_clipper())
m_assemble_view_data->model_objects_clipper()->render_cut();
}
// The icon bar, drawn with GL.
void GLGizmosManager::render_overlay()
{
+13 -5
View File
@@ -22,6 +22,7 @@
#include <cereal/specialize.hpp>
#include <wx/timer.h>
#include <map>
#include <optional>
//BBS: GUI refactor: to support top layout
#define BBS_TOOLBAR_ON_TOP 1
@@ -181,7 +182,6 @@ private:
bool gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event);
public:
std::unique_ptr<AssembleViewDataPool> m_assemble_view_data;
enum MENU_ICON_NAME {
IC_TOOLBAR_RESET = 0,
IC_TOOLBAR_RESET_HOVER,
@@ -198,6 +198,14 @@ public:
IC_CANVAS_ZOOM_HOVER,
IC_CANVAS_ZOOM_DARK,
IC_CANVAS_ZOOM_DARK_HOVER,
IC_CANVAS_SECTION,
IC_CANVAS_SECTION_HOVER,
IC_CANVAS_SECTION_DARK,
IC_CANVAS_SECTION_DARK_HOVER,
IC_CANVAS_SECTION_ACTIVE,
IC_CANVAS_SECTION_ACTIVE_HOVER,
IC_CANVAS_SECTION_ACTIVE_DARK,
IC_CANVAS_SECTION_ACTIVE_DARK_HOVER,
};
explicit GLGizmosManager(GLCanvas3D& parent);
@@ -266,7 +274,8 @@ public:
/// Should be called when selection changed
/// </summary>
void update_data();
void update_assemble_view_data();
// Passes the canvas' section view to the object clipper of the painting and brim ears gizmos.
void update_section_view();
EType get_current_type() const { return m_current; }
GLGizmoBase* get_current() const;
@@ -297,8 +306,8 @@ public:
bool is_paint_gizmo();
bool is_allow_select_all();
ClippingPlane get_clipping_plane() const;
ClippingPlane get_assemble_view_clipping_plane() const;
// Empty when the open gizmo has no object clipper.
std::optional<ClippingPlane> get_clipping_plane() const;
bool wants_reslice_supports_on_undo() const;
bool is_in_editing_mode(bool error_notification = false) const;
@@ -307,7 +316,6 @@ public:
void on_change_color_mode(bool is_dark);
void render_current_gizmo() const;
void render_painter_gizmo();
void render_painter_assemble_view() const;
void render_overlay();
void render_overlay_input_window();
+2 -1
View File
@@ -196,13 +196,13 @@ void KBShortcutsDialog::fill_pages()
mouse(Section::Camera, _L("Middle mouse"), "middle_mouse_drag_action"),
mouse(Section::Camera, _L("Right mouse"), "right_mouse_drag_action"),
fixed(Section::Camera, { wheel }, L("Zoom View")),
fixed(Section::Display, { alt, wheel }, L("Move section plane")),
});
page(_L("Painting"), _L("Available while a painting gizmo is open: supports, seam, fuzzy skin or color painting."), ShortcutContext::Painting, {
fixed(Section::Gizmos, { esc }, L("Deselect All")),
fixed(Section::Gizmos, { shift, left_button }, L("Move: press to snap by 1mm")),
fixed(Section::PaintingTools, { ctrl, wheel }, L("Support/Color Painting: adjust pen radius")),
fixed(Section::PaintingTools, { alt, wheel }, L("Support/Color Painting: adjust section position")),
});
page(_L("Objects list"), _L("Available while the object list has focus."), ShortcutContext::ObjectList, {
@@ -216,6 +216,7 @@ void KBShortcutsDialog::fill_pages()
page(_L("Preview"), _L("Available while the 3D view on the Preview tab has focus."), ShortcutContext::Preview, {
fixed(Section::Sliders, { shift_ctrl, any_key }, L("Move slider 5x faster")),
fixed(Section::Sliders, { shift_ctrl, wheel }, L("Scroll slider 5x faster")),
fixed(Section::Display, { alt, wheel }, L("Move section plane")),
});
}
+5
View File
@@ -117,6 +117,7 @@
#include "NotificationManager.hpp"
#include "MarkdownTip.hpp"
#include "NetworkTestDialog.hpp"
#include "SceneBenchmark.hpp"
#include "ConfigWizard.hpp"
#include "Widgets/WebView.hpp"
#include "DailyTips.hpp"
@@ -2786,6 +2787,10 @@ wxMenu* MainFrame::generate_help_menu()
dlg.ShowModal();
});
if (wxGetApp().is_editor())
append_menu_item(helpMenu, wxID_ANY, _L("Benchmark 3D Scene"), _L("Measure how fast the 3D scene renders in Prepare and Preview"),
[](wxCommandEvent&) { run_scene_benchmark(); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the Day"), _L("Show Tip of the Day"), [](wxCommandEvent&) {
+5
View File
@@ -17,6 +17,7 @@
#include "Plater.hpp"
#include "PluginsDialog.hpp"
#include "PlateSettingsDialog.hpp"
#include "SceneBenchmark.hpp"
#include "DeviceCore/DevManager.h"
#include "libslic3r/Config.hpp"
@@ -591,6 +592,10 @@ std::vector<NativeCommand> build_command_catalog()
dlg.ShowModal();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_benchmark_3d_scene", _u8L("Benchmark 3D Scene"), _u8L("Help"), [](const std::string&) {
run_scene_benchmark();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_tip_of_the_day", _u8L("Show Tip of the Day"), _u8L("Help"), "info", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
plater->get_dailytips()->open();
+1
View File
@@ -7894,6 +7894,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D = new View3D(panel_3d, bed, &model, config, &background_process);
//BBS: use partplater's gcode
preview = new Preview(panel_3d, bed, &model, config, &background_process, partplate_list.get_current_slice_result(), [this]() { schedule_background_process(); });
preview->get_canvas3d()->share_section_view(*view3D->get_canvas3d());
assemble_view = new AssembleView(panel_3d, bed, &model, config, &background_process);
+28 -4
View File
@@ -65,6 +65,7 @@
#include <wx/types.h>
#include <wx/timer.h>
#include "NetworkTestDialog.hpp"
#include "SceneBenchmark.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/RadioGroup.hpp"
#include "Shortcuts.hpp"
@@ -1999,11 +2000,14 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_realistic_ssao);
auto item_realistic_shadows = create_item_checkbox(
std::vector<wxString> ShadowsLabels = { _L("Off"), _L("Static"), _L("Orbit") };
std::vector<std::string> ShadowsValues = { "off", "static", "orbit" };
auto item_realistic_shadows = create_item_combobox(
_L("Shadows"),
_L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view."),
SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS
);
_L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view.\n"
"Static: the light stays fixed in the world, so the shadows are only recomputed when the scene changes.\n"
"Orbit: the light turns with the camera, recomputing the shadows every frame the camera moves."),
SETTING_OPENGL_REALISTIC_SHADOWS, ShadowsLabels, ShadowsValues);
g_sizer->Add(item_realistic_shadows);
//// GRAPHICS > Anti-aliasing
@@ -2076,6 +2080,26 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_fps_overlay);
auto item_render_timings = create_item_checkbox(
_L("Show render timings"),
_L("Displays how many milliseconds each part of a frame that redraws the 3D scene takes, in the top-right corner of the viewport.") + "\n" +
_L("CPU: time spent issuing the drawing commands.") + "\n" +
_L("GPU: time the graphics card spent running them.") + "\n" +
_L("Adds a small overhead to each frame while enabled."),
SETTING_OPENGL_SHOW_RENDER_TIMINGS
);
g_sizer->Add(item_render_timings);
if (wxGetApp().is_editor()) {
auto item_benchmark = create_item_button(_L("3D scene benchmark"), _L("Run") + " " + dots, "",
_L("Replaces the current project with the OrcaSliced Combo, then measures the frame rate and render timings while the camera turns around it in Prepare and Preview, and while the layer slider moves through the sliced layers."),
[this]() {
EndModal(wxID_OK);
wxGetApp().CallAfter([] { run_scene_benchmark(); });
});
g_sizer->Add(item_benchmark);
}
//// GRAPHICS > G-code Preview
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
+555
View File
@@ -0,0 +1,555 @@
#include "SceneBenchmark.hpp"
#include "3DScene.hpp"
#include "BuildCommit.hpp"
#include "Camera.hpp"
#include "GLCanvas3D.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Factories.hpp"
#include "I18N.hpp"
#include "IMSlider.hpp"
#include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include "NotificationManager.hpp"
#include "OpenGLManager.hpp"
#include "Plater.hpp"
#include "Widgets/DialogButtons.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/ProgressBar.hpp"
#include "Widgets/StateColor.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/libslic3r.h"
#include <glad/gl.h>
#include <wx/clipbrd.h>
#include <wx/glcanvas.h>
#include <wx/textctrl.h>
#include <wx/timer.h>
#include <wx/utils.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <memory>
#include <numeric>
#include <sstream>
namespace Slic3r {
namespace GUI {
FrameTimeStats frame_time_stats(std::vector<double> frame_ms)
{
FrameTimeStats stats;
if (frame_ms.empty())
return stats;
std::sort(frame_ms.begin(), frame_ms.end());
const double total_ms = std::accumulate(frame_ms.begin(), frame_ms.end(), 0.0);
auto percentile = [&frame_ms](double p) {
const size_t rank = size_t(std::ceil(p * double(frame_ms.size()) / 100.0));
return frame_ms[std::clamp<size_t>(rank, 1, frame_ms.size()) - 1];
};
stats.fps = total_ms > 0.0 ? 1000.0 * double(frame_ms.size()) / total_ms : 0.0;
stats.average_ms = total_ms / double(frame_ms.size());
stats.median_ms = percentile(50.0);
stats.p95_ms = percentile(95.0);
stats.p99_ms = percentile(99.0);
stats.max_ms = frame_ms.back();
return stats;
}
namespace {
constexpr size_t WARMUP_FRAMES = 30;
// A scene runs the camera path twice: timing the frames, then averaging the render timings.
constexpr size_t PASS_FRAMES = 360;
constexpr size_t SCENE_FRAMES = WARMUP_FRAMES + 2 * PASS_FRAMES;
struct SceneResult
{
std::string name;
std::vector<double> frame_ms;
std::vector<FrameProfiler::Section> sections;
};
class SceneBenchmarkDialog : public DPIDialog
{
public:
SceneBenchmarkDialog();
~SceneBenchmarkDialog() override;
bool is_running() const { return m_stage != Stage::Done; }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
enum class Stage { Loading, Prepare, Slicing, Preview, Layers, Done };
void on_timer(wxTimerEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_first_frame();
void begin_scene(GLCanvas3D* canvas, Stage stage);
void end_scene();
void stop_scene();
void start_slicing();
void set_camera(size_t pass_frame);
void set_top_layer(size_t pass_frame);
void set_status(const wxString& status);
void finish(const wxString& error = wxString());
void show_report(const wxString& error);
std::string report() const;
Stage m_stage{ Stage::Loading };
std::unique_ptr<wxWindowDisabler> m_disabler;
wxTimer m_timer;
int m_polls{ 0 };
Label* m_status{ nullptr };
ProgressBar* m_progress{ nullptr };
GLCanvas3D* m_canvas{ nullptr };
Camera m_saved_camera;
Vec3d m_target{ Vec3d::Zero() };
double m_zoom{ 1.0 };
size_t m_frame{ 0 };
std::chrono::steady_clock::time_point m_last_frame;
// The layer slider's top before the Layers scene moved it, -1 outside that scene.
int m_saved_top_layer{ -1 };
int m_swap_interval{ wxGLCanvas::DefaultSwapInterval };
bool m_restore_swap_interval{ false };
std::vector<SceneResult> m_results;
// Read on the first frame, with the context current.
std::string m_vsync;
int m_msaa_samples{ 0 };
int m_viewport_width{ 0 };
int m_viewport_height{ 0 };
std::string m_camera_type;
};
SceneBenchmarkDialog* s_benchmark = nullptr;
SceneBenchmarkDialog::SceneBenchmarkDialog()
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, _L("Benchmark 3D Scene"), wxDefaultPosition, wxDefaultSize,
wxCAPTION | wxCLOSE_BOX)
, m_timer(this)
{
s_benchmark = this;
SetBackgroundColour(*wxWHITE);
SetFont(Label::Body_14);
auto* sizer = new wxBoxSizer(wxVERTICAL);
sizer->SetMinSize(wxSize(FromDIP(360), -1));
m_status = new Label(this, _L("Loading the benchmark model") + dots);
m_progress = new ProgressBar(this, wxID_ANY, 100);
m_progress->SetHeight(FromDIP(8));
m_progress->SetMaxSize(wxSize(-1, FromDIP(8)));
m_progress->SetProgressForedColour(StateColor::darkModeColorFor(wxColour("#DFDFDF")));
sizer->Add(m_status, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
sizer->Add(m_progress, 0, wxEXPAND | wxALL, FromDIP(15));
auto* buttons = new DialogButtons(this, {"Cancel"});
buttons->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { Close(); });
sizer->Add(buttons, 0, wxEXPAND);
SetSizerAndFit(sizer);
wxGetApp().UpdateDlgDarkUI(this);
Bind(wxEVT_TIMER, &SceneBenchmarkDialog::on_timer, this);
Bind(wxEVT_IDLE, &SceneBenchmarkDialog::on_idle, this);
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& evt) {
if (evt.GetKeyCode() == WXK_ESCAPE)
Close();
else
evt.Skip();
});
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent&) {
m_timer.Stop();
stop_scene();
m_disabler.reset();
Destroy();
});
// A corner of the 3D view, away from the model.
const wxRect view = wxGetApp().plater()->get_current_canvas3D()->get_wxglcanvas()->GetScreenRect();
SetPosition(wxPoint(view.GetLeft() + FromDIP(20), view.GetBottom() - GetSize().GetHeight() - FromDIP(20)));
m_disabler = std::make_unique<wxWindowDisabler>(this);
m_timer.Start(100);
}
SceneBenchmarkDialog::~SceneBenchmarkDialog()
{
// At shutdown the windows it disabled may be gone already.
if (wxGetApp().is_closing())
(void) m_disabler.release();
if (s_benchmark == this)
s_benchmark = nullptr;
}
void SceneBenchmarkDialog::on_dpi_changed(const wxRect&)
{
Fit();
Refresh();
}
void SceneBenchmarkDialog::on_timer(wxTimerEvent&)
{
Plater* plater = wxGetApp().plater();
if (wxGetApp().is_closing() || plater == nullptr) {
m_timer.Stop();
return;
}
if (m_stage == Stage::Loading) {
if (!plater->get_ui_job_worker().is_idle())
return;
m_timer.Stop();
GLCanvas3D* canvas = plater->get_view3D_canvas3D();
const BoundingBoxf3 box = canvas->volumes_bounding_box(true);
if (!box.defined) {
finish(_L("The benchmark model could not be loaded."));
return;
}
m_target = box.center();
const Size size = canvas->get_canvas_size();
m_zoom = double(std::min(size.get_width(), size.get_height())) / (1.1 * box.size().norm());
plater->deselect_all();
begin_scene(canvas, Stage::Prepare);
} else if (m_stage == Stage::Slicing) {
if (plater->is_background_process_slicing())
return;
GLCanvas3D* canvas = plater->get_preview_canvas3D();
if (canvas->get_gcode_viewer().has_data() && canvas->get_wxglcanvas()->IsShownOnScreen()) {
m_timer.Stop();
plater->get_notification_manager()->set_slicing_progress_hidden();
begin_scene(canvas, Stage::Preview);
} else if (++m_polls > 30)
finish(_L("Slicing failed, so only Prepare was measured."));
}
}
void SceneBenchmarkDialog::on_idle(wxIdleEvent& evt)
{
evt.Skip();
if (m_canvas == nullptr || wxGetApp().is_closing())
return;
if (!m_canvas->get_wxglcanvas()->IsShownOnScreen()) {
finish(_L("The benchmark stopped because the 3D view was hidden."));
return;
}
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
if (m_frame > WARMUP_FRAMES && m_frame <= WARMUP_FRAMES + PASS_FRAMES)
m_results.back().frame_ms.push_back(std::chrono::duration<double, std::milli>(now - m_last_frame).count());
m_last_frame = now;
FrameProfiler& profiler = m_canvas->get_frame_profiler();
if (m_frame == WARMUP_FRAMES + PASS_FRAMES)
profiler.start_averaging();
if (m_stage == Stage::Layers)
// The warm-up ends where the path starts, so the slider moves in every frame.
set_top_layer(m_frame < WARMUP_FRAMES ? PASS_FRAMES - WARMUP_FRAMES + m_frame : (m_frame - WARMUP_FRAMES) % PASS_FRAMES);
else
set_camera(m_frame < WARMUP_FRAMES ? 0 : (m_frame - WARMUP_FRAMES) % PASS_FRAMES);
m_canvas->render();
if (m_frame == 0)
on_first_frame();
if (++m_frame == SCENE_FRAMES) {
m_results.back().sections = profiler.finish_averaging(true);
end_scene();
// The next scene starts right away, with no timer to bring the next idle event.
if (m_canvas != nullptr)
evt.RequestMore();
return;
}
const int scene = m_stage == Stage::Prepare ? 0 : m_stage == Stage::Preview ? 1 : 2;
if (m_frame % 30 == 0)
m_progress->SetValue(int(100 * (scene * SCENE_FRAMES + m_frame) / (3 * SCENE_FRAMES)));
evt.RequestMore();
}
void SceneBenchmarkDialog::on_first_frame()
{
// A benchmark measures the frames the GPU can draw, not the display refresh rate.
wxGLCanvas* glcanvas = m_canvas->get_wxglcanvas();
m_swap_interval = glcanvas->GetSwapInterval();
const bool known = m_swap_interval != wxGLCanvas::DefaultSwapInterval;
if (known && m_swap_interval != 0)
m_restore_swap_interval = glcanvas->SetSwapInterval(0) != wxGLCanvas::SwapInterval::NotSet;
if (m_results.size() > 1)
return;
m_vsync = !known ? "unknown" : m_swap_interval == 0 || m_restore_swap_interval ? "off" : "on";
GLint samples = 0;
glsafe(::glGetIntegerv(GL_SAMPLES, &samples));
m_msaa_samples = samples;
const Camera& camera = wxGetApp().plater()->get_camera();
const std::array<int, 4>& viewport = camera.get_viewport();
m_viewport_width = viewport[2];
m_viewport_height = viewport[3];
m_camera_type = camera.get_type_as_string();
}
void SceneBenchmarkDialog::begin_scene(GLCanvas3D* canvas, Stage stage)
{
m_stage = stage;
m_canvas = canvas;
m_saved_camera = wxGetApp().plater()->get_camera();
m_frame = 0;
m_restore_swap_interval = false;
m_results.push_back({stage == Stage::Prepare ? "Prepare" : stage == Stage::Preview ? "Preview" : "Layers", {}, {}});
m_results.back().frame_ms.reserve(PASS_FRAMES);
set_status(stage == Stage::Prepare ? _L("Turning the camera in Prepare") :
stage == Stage::Preview ? _L("Turning the camera in Preview") :
_L("Moving through the layers in Preview"));
if (stage == Stage::Layers) {
m_saved_top_layer = canvas->get_gcode_viewer().get_layers_slider()->GetHigherValue();
set_camera(0);
}
canvas->set_benchmarking(true);
}
void SceneBenchmarkDialog::end_scene()
{
const Stage stage = m_stage;
GLCanvas3D* canvas = m_canvas;
stop_scene();
if (stage == Stage::Prepare)
start_slicing();
else if (stage == Stage::Preview)
begin_scene(canvas, Stage::Layers);
else
finish();
}
void SceneBenchmarkDialog::stop_scene()
{
if (m_canvas == nullptr)
return;
if (m_frame < SCENE_FRAMES)
m_results.pop_back();
FrameProfiler& profiler = m_canvas->get_frame_profiler();
if (profiler.is_averaging())
profiler.finish_averaging(false);
if (m_restore_swap_interval && m_canvas->make_current_for_postinit())
m_canvas->get_wxglcanvas()->SetSwapInterval(m_swap_interval);
if (m_saved_top_layer >= 0) {
m_canvas->get_gcode_viewer().get_layers_slider()->SetHigherValue(m_saved_top_layer);
m_saved_top_layer = -1;
}
m_canvas->set_benchmarking(false);
m_canvas->set_as_dirty();
wxGetApp().plater()->get_camera() = m_saved_camera;
m_canvas = nullptr;
}
void SceneBenchmarkDialog::start_slicing()
{
m_stage = Stage::Slicing;
set_status(_L("Slicing"));
m_progress->SetValue(33);
Plater* plater = wxGetApp().plater();
plater->reslice();
plater->select_view_3D("Preview", false);
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
m_polls = 0;
m_timer.Start(100);
}
void SceneBenchmarkDialog::set_camera(size_t pass_frame)
{
// Two turns around the model while the view goes three times from below the plate to nearly
// straight above it, zooming in and out twice.
const double t = double(pass_frame) / double(PASS_FRAMES);
const double azimuth = (-0.75 + 4.0 * t) * PI;
const double elevation = (30.0 + 55.0 * std::sin(6.0 * PI * t)) * PI / 180.0;
const Vec3d dir(std::cos(elevation) * std::cos(azimuth), std::cos(elevation) * std::sin(azimuth), std::sin(elevation));
Camera& camera = wxGetApp().plater()->get_camera();
camera.look_at(m_target + Camera::DefaultDistance * dir, m_target, Vec3d::UnitZ());
camera.set_zoom(m_zoom * (1.0 + 0.4 * std::sin(4.0 * PI * t)));
}
void SceneBenchmarkDialog::set_top_layer(size_t pass_frame)
{
// Down to the first layer and back up, as dragging the top of the layer slider does.
IMSlider* slider = m_canvas->get_gcode_viewer().get_layers_slider();
const double t = double(pass_frame) / double(PASS_FRAMES);
const int span = slider->GetMaxValue() - slider->GetMinValue();
slider->SetHigherValue(slider->GetMinValue() + int(std::lround(span * std::abs(1.0 - 2.0 * t))));
}
void SceneBenchmarkDialog::set_status(const wxString& status)
{
m_status->SetLabel(status + dots);
Fit();
}
void SceneBenchmarkDialog::finish(const wxString& error)
{
m_timer.Stop();
stop_scene();
m_disabler.reset();
m_stage = Stage::Done;
show_report(error);
}
void SceneBenchmarkDialog::show_report(const wxString& error)
{
DestroyChildren();
m_status = nullptr;
m_progress = nullptr;
auto* sizer = new wxBoxSizer(wxVERTICAL);
if (!error.IsEmpty()) {
auto* message = new Label(this, error);
message->Wrap(FromDIP(500));
sizer->Add(message, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
}
std::string text;
if (!m_results.empty()) {
text = report();
auto* content = new wxTextCtrl(this, wxID_ANY, from_u8(text), wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP | wxBORDER_SIMPLE);
content->SetFont(wxGetApp().code_font());
wxClientDC dc(content);
dc.SetFont(content->GetFont());
const wxSize extent = dc.GetMultiLineTextExtent(content->GetValue()) + FromDIP(wxSize(40, 20));
content->SetMinSize(wxSize(extent.GetWidth(), std::min(extent.GetHeight(), FromDIP(560))));
sizer->Add(content, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
}
auto* buttons = new DialogButtons(this, text.empty() ? std::vector<wxString>{"OK"} : std::vector<wxString>{"Copy", "OK"});
if (Button* copy = buttons->GetButtonFromID(wxID_COPY)) {
copy->SetToolTip(_L("Copy the results to the clipboard"));
copy->Bind(wxEVT_BUTTON, [text](wxCommandEvent&) {
wxClipboardLocker lock;
if (!lock)
return;
wxTheClipboard->SetData(new wxTextDataObject(from_u8(text)));
});
}
buttons->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { Close(); });
sizer->Add(buttons, 0, wxEXPAND);
SetSizerAndFit(sizer);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
Raise();
}
std::string SceneBenchmarkDialog::report() const
{
const AppConfig& config = *wxGetApp().app_config;
const OpenGLManager::GLInfo& gl = OpenGLManager::get_gl_info();
auto on_off = [](bool on) { return on ? "on" : "off"; };
std::ostringstream out;
out << SLIC3R_APP_NAME << " 3D scene benchmark\n"
<< "Version: " << SoftFever_VERSION << " (" << build_commit_label << ")\n"
<< "GPU: " << gl.get_renderer() << '\n'
<< "OpenGL: " << gl.get_version() << '\n'
<< "Viewport: " << m_viewport_width << " x " << m_viewport_height << " px, " << m_camera_type << " camera\n"
<< "Settings: MSAA " << (m_msaa_samples > 1 ? std::to_string(m_msaa_samples) + "x" : "off")
<< ", FXAA " << on_off(config.get_bool(SETTING_OPENGL_FXAA_ENABLED))
<< ", scene cache " << on_off(config.get_bool(SETTING_OPENGL_SCENE_CACHE)) << ", VSync " << m_vsync << '\n'
<< "Realistic: ";
if (config.get_bool(SETTING_OPENGL_REALISTIC_MODE))
out << "on, Phong " << on_off(config.get_bool(SETTING_OPENGL_REALISTIC_PHONG)) << ", SSAO "
<< on_off(config.get_bool(SETTING_OPENGL_PHONG_SSAO)) << ", shadows " << config.get(SETTING_OPENGL_REALISTIC_SHADOWS)
<< ", in Preview " << on_off(config.get_bool(SETTING_OPENGL_REALISTIC_PREVIEW)) << '\n';
else
out << "off\n";
// The Preview scenes depend on the toolpaths, which depend on the presets it was sliced with.
const PresetBundle& presets = *wxGetApp().preset_bundle;
auto preset_name = [](const PresetCollection& collection) {
return collection.get_selected_preset_name() + (collection.get_edited_preset().is_dirty ? " (modified)" : "");
};
out << "Presets: " << preset_name(presets.printers) << ", " << preset_name(presets.prints) << '\n'
<< "Scene: OrcaSliced Combo, " << PASS_FRAMES << " frames per pass\n";
const GCodeViewer& gcode_viewer = wxGetApp().plater()->get_preview_canvas3D()->get_gcode_viewer();
if (gcode_viewer.has_data())
out << "Toolpaths: " << gcode_viewer.get_vertices_count() << " vertices, " << gcode_viewer.get_layers_count() << " layers\n";
out << '\n';
std::vector<FrameTimeStats> stats;
for (const SceneResult& result : m_results)
stats.push_back(frame_time_stats(result.frame_ms));
auto row = [&out, &stats](const char* label, double FrameTimeStats::*value) {
out << std::left << std::setw(22) << label << std::right;
for (const FrameTimeStats& s : stats)
out << std::setw(10) << s.*value;
out << '\n';
};
out << std::fixed << std::setprecision(1) << std::setw(22) << "";
for (const SceneResult& result : m_results)
out << std::right << std::setw(10) << result.name;
out << '\n';
row("Average FPS", &FrameTimeStats::fps);
out << std::setprecision(2) << "Frame time (ms)\n";
row(" average", &FrameTimeStats::average_ms);
row(" median", &FrameTimeStats::median_ms);
row(" 95th percentile", &FrameTimeStats::p95_ms);
row(" 99th percentile", &FrameTimeStats::p99_ms);
row(" maximum", &FrameTimeStats::max_ms);
for (const SceneResult& result : m_results) {
out << "\nRender timings, " << result.name << " (ms per frame)\n";
if (result.sections.empty()) {
out << " not supported by the graphics driver\n";
continue;
}
out << std::setw(22) << "" << std::setw(10) << "CPU" << std::setw(10) << "GPU" << '\n';
double cpu_ms = 0.0;
double gpu_ms = 0.0;
for (const FrameProfiler::Section& section : result.sections) {
out << " " << std::left << std::setw(20) << section.name << std::right << std::setw(10) << section.cpu_ms << std::setw(10)
<< section.gpu_ms << '\n';
cpu_ms += section.cpu_ms;
gpu_ms += section.gpu_ms;
}
out << " " << std::left << std::setw(20) << "total" << std::right << std::setw(10) << cpu_ms << std::setw(10) << gpu_ms << '\n';
}
return out.str();
}
} // namespace
void run_scene_benchmark()
{
Plater* plater = wxGetApp().plater();
if (plater == nullptr || !wxGetApp().is_editor())
return;
if (s_benchmark != nullptr) {
if (s_benchmark->is_running()) {
s_benchmark->Raise();
return;
}
s_benchmark->Close();
}
MessageDialog confirm(wxGetApp().mainframe,
_L("The benchmark replaces the current project with the OrcaSliced Combo and slices it. It turns the camera "
"around the model in Prepare and in Preview, then moves the layer slider through the sliced layers, timing "
"every frame, and shows the results.") + "\n\n" +
_L("It uses the current graphics settings and usually takes less than a minute. The main window stays "
"locked until it finishes."),
_L("Benchmark 3D Scene"), wxICON_INFORMATION | wxOK | wxCANCEL);
confirm.SetButtonLabel(wxID_OK, _L("Start"), true);
if (confirm.ShowModal() != wxID_OK || plater->new_project() == wxID_CANCEL)
return;
const std::vector<MenuFactory::HandyModel>& models = MenuFactory::handy_models();
const auto combo = std::find_if(models.begin(), models.end(),
[](const MenuFactory::HandyModel& model) { return std::strcmp(model.key, "orcasliced_combo") == 0; });
MenuFactory::load_handy_model(size_t(combo - models.begin()));
(new SceneBenchmarkDialog())->Show();
}
} // namespace GUI
} // namespace Slic3r
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <vector>
namespace Slic3r {
namespace GUI {
struct FrameTimeStats
{
double fps{ 0.0 };
double average_ms{ 0.0 };
double median_ms{ 0.0 };
double p95_ms{ 0.0 };
double p99_ms{ 0.0 };
double max_ms{ 0.0 };
};
// Nearest-rank percentiles, so every value is a measured frame time.
FrameTimeStats frame_time_stats(std::vector<double> frame_ms);
// Loads the OrcaSliced Combo into a new project and turns the camera around it in Prepare, then in
// Preview once sliced, then moves the layer slider through the layers, and reports the frame times and
// render timings of the three scenes.
void run_scene_benchmark();
} // namespace GUI
} // namespace Slic3r
+103
View File
@@ -558,6 +558,109 @@ SCENARIO("update_diff_values_to_child_config tolerates legacy machine-limit vect
}
}
TEST_CASE("A variant index comes from the same variant and id, else the id's first variant", "[Config][Variant]") {
const std::vector<std::string> variant_list{"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard"};
const std::vector<int> variant_ids{1, 1, 2};
SECTION("same variant and id") {
CHECK(Slic3r::find_variant_index("Direct Drive High Flow", 1, variant_list, variant_ids) == 1);
CHECK(Slic3r::find_variant_index("Direct Drive Standard", 2, variant_list, variant_ids) == 2);
}
SECTION("a variant the id lacks falls back to the id's first variant") {
CHECK(Slic3r::find_variant_index("Bowden Standard", 1, variant_list, variant_ids) == 0);
CHECK(Slic3r::find_variant_index("Direct Drive High Flow", 2, variant_list, variant_ids) == 2);
}
SECTION("an id with no variants matches none") {
CHECK(Slic3r::find_variant_index("Direct Drive Standard", 3, variant_list, variant_ids) == -1);
}
SECTION("a negative id or a list without ids matches any id") {
CHECK(Slic3r::find_variant_index("Direct Drive High Flow", -1, variant_list, variant_ids) == 1);
CHECK(Slic3r::find_variant_index("Direct Drive High Flow", 2, variant_list, {}) == 1);
}
SECTION("a variant past a shorter id list gets no variant index") {
CHECK(Slic3r::map_variant_indices(variant_list, {1}, variant_list, variant_ids) == std::vector<int>{0, -1, -1});
}
SECTION("a list without variant strings has one variant per id, and an empty one a single variant") {
CHECK(Slic3r::map_variant_indices(variant_list, variant_ids, {}, {1, 2}) == std::vector<int>{0, 0, 1});
CHECK(Slic3r::map_variant_indices(variant_list, variant_ids, {}, {}) == std::vector<int>{0, 0, 0});
}
}
SCENARIO("update_diff_values_to_child_config keeps a child's values on variants it does not list",
"[Config][Variant]") {
std::set<std::string> no_keys;
auto variants = [](std::initializer_list<std::string> names) { return new Slic3r::ConfigOptionStrings(names); };
GIVEN("A filament parent with three variants") {
Slic3r::DynamicPrintConfig parent;
parent.set_key_value("filament_extruder_variant",
variants({"Direct Drive Standard", "Bowden Standard", "Direct Drive High Flow"}));
parent.set_deserialize_strict("nozzle_temperature", "220,220,220");
WHEN("the child was saved when the parent had only its first variant") {
Slic3r::DynamicPrintConfig child;
child.set_key_value("filament_extruder_variant", variants({"Direct Drive Standard"}));
child.set_deserialize_strict("nozzle_temperature", "199");
parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant",
Slic3r::filament_options_with_variant, no_keys);
THEN("the child's value applies to every variant") {
REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,199,199");
}
}
WHEN("the child lists every variant, in another order") {
Slic3r::DynamicPrintConfig child;
child.set_key_value("filament_extruder_variant",
variants({"Bowden Standard", "Direct Drive High Flow", "Direct Drive Standard"}));
child.set_deserialize_strict("nozzle_temperature", "190,205,199");
parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant",
Slic3r::filament_options_with_variant, no_keys);
THEN("each variant keeps its own value") {
REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,190,205");
}
}
WHEN("the child lists no variants") {
Slic3r::DynamicPrintConfig child;
child.set_deserialize_strict("nozzle_temperature", "199");
parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant",
Slic3r::filament_options_with_variant, no_keys);
THEN("the child's value applies to every variant") {
REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,199,199");
}
}
}
GIVEN("A two-extruder printer parent with two variants per extruder") {
Slic3r::DynamicPrintConfig parent;
parent.set_key_value("printer_extruder_variant",
variants({"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard", "Direct Drive High Flow"}));
parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2}));
parent.set_deserialize_strict("retraction_length", "0.8,0.8,0.8,0.8");
WHEN("the child lists only the Standard variant of each extruder") {
Slic3r::DynamicPrintConfig child;
child.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"}));
child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
child.set_deserialize_strict("retraction_length", "1.1,2.2");
parent.update_diff_values_to_child_config(child, "printer_extruder_id", "printer_extruder_variant",
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("each extruder's High Flow variant takes that extruder's value") {
REQUIRE(parent.opt_serialize("retraction_length") == "1.1,1.1,2.2,2.2");
}
}
WHEN("the child lists no variants") {
Slic3r::DynamicPrintConfig child;
child.set_deserialize_strict("retraction_length", "1.1");
parent.update_diff_values_to_child_config(child, "printer_extruder_id", "printer_extruder_variant",
Slic3r::printer_options_with_variant_1,
Slic3r::printer_options_with_variant_2);
THEN("only the first extruder's variants take the child's value") {
REQUIRE(parent.opt_serialize("retraction_length") == "1.1,1.1,0.8,0.8");
}
}
}
}
// SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") {
// WHEN("DynamicPrintConfig is serialized and deserialized") {
// auto now = std::chrono::high_resolution_clock::now();
+1
View File
@@ -9,6 +9,7 @@ add_executable(${_TEST_NAME}_tests
test_device_manager_integration.cpp
test_web_media_controller.cpp
test_scene_raycaster.cpp
test_scene_benchmark.cpp
test_lazy.cpp
test_prebuild_queue.cpp
test_staged_build.cpp
+2 -1
View File
@@ -238,7 +238,8 @@ TEST_CASE("Native command catalog includes the Help and developer-mode commands"
const Slic3r::GUI::NativeCommand* first = find("help_keyboard_shortcuts");
REQUIRE(first != nullptr);
for (const char* key : {"help_setup_wizard", "help_open_config_folder", "help_troubleshoot", "help_network_test",
"help_tip_of_the_day", "help_check_updates", "help_about", "open_wiki", "open_youtube"}) {
"help_benchmark_3d_scene", "help_tip_of_the_day", "help_check_updates", "help_about", "open_wiki",
"open_youtube"}) {
const Slic3r::GUI::NativeCommand* c = find(key);
REQUIRE(c != nullptr);
CHECK(c->group == first->group);
@@ -0,0 +1,57 @@
#include <catch2/catch_all.hpp>
#include "slic3r/GUI/SceneBenchmark.hpp"
#include <algorithm>
#include <numeric>
#include <random>
#include <vector>
using Catch::Matchers::WithinAbs;
using Slic3r::GUI::frame_time_stats;
using Slic3r::GUI::FrameTimeStats;
TEST_CASE("Frame time statistics take percentiles by nearest rank", "[SceneBenchmark]")
{
// 1 to 100 ms in shuffled order: the p-th percentile is the p-th frame time.
std::vector<double> frame_ms(100);
std::iota(frame_ms.begin(), frame_ms.end(), 1.0);
std::shuffle(frame_ms.begin(), frame_ms.end(), std::mt19937(42));
const FrameTimeStats stats = frame_time_stats(frame_ms);
CHECK_THAT(stats.average_ms, WithinAbs(50.5, 1e-9));
CHECK_THAT(stats.fps, WithinAbs(1000.0 / 50.5, 1e-9));
CHECK_THAT(stats.median_ms, WithinAbs(50.0, 1e-9));
CHECK_THAT(stats.p95_ms, WithinAbs(95.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(99.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(100.0, 1e-9));
}
TEST_CASE("Frame time percentiles are measured frame times", "[SceneBenchmark]")
{
// 360 frames, as in a benchmark pass: rank 342 for p95 and rank 357 for p99.
std::vector<double> frame_ms(360);
std::iota(frame_ms.begin(), frame_ms.end(), 1.0);
const FrameTimeStats stats = frame_time_stats(frame_ms);
CHECK_THAT(stats.median_ms, WithinAbs(180.0, 1e-9));
CHECK_THAT(stats.p95_ms, WithinAbs(342.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(357.0, 1e-9));
}
TEST_CASE("Frame time statistics of a single frame are that frame", "[SceneBenchmark]")
{
const FrameTimeStats stats = frame_time_stats({ 4.0 });
CHECK_THAT(stats.fps, WithinAbs(250.0, 1e-9));
CHECK_THAT(stats.median_ms, WithinAbs(4.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(4.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(4.0, 1e-9));
}
TEST_CASE("Frame time statistics of no frames are zero", "[SceneBenchmark]")
{
const FrameTimeStats stats = frame_time_stats({});
CHECK_THAT(stats.fps, WithinAbs(0.0, 1e-9));
CHECK_THAT(stats.average_ms, WithinAbs(0.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(0.0, 1e-9));
}