mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 11:07:40 +00:00
fix(automation): capture screenshot.window from the composited screen
Blitting from the MainFrame's own wxClientDC clips out child HWNDs, so all of OrcaSlicer's custom child-window controls (sidebar buttons/combos/panels) and the GL canvas came back as uninitialized black bitmap memory. Read the window's on-screen rectangle from the DWM-composited desktop via wxScreenDC instead, which includes every child window, the OpenGL surface, and ImGui overlays. Document the visible/unobscured requirement and the HiDPI logical-vs-physical pixel caveat; clarify how screenshot.viewport3d differs and why it stays.
This commit is contained in:
@@ -267,7 +267,7 @@ Return a high-level application-state snapshot. Takes no parameters.
|
|||||||
|
|
||||||
### `screenshot.window`
|
### `screenshot.window`
|
||||||
|
|
||||||
Capture a window's own GDI/native surface as a PNG.
|
Capture a window as a PNG, exactly as it appears on screen.
|
||||||
|
|
||||||
**Params:**
|
**Params:**
|
||||||
|
|
||||||
@@ -280,15 +280,31 @@ Capture a window's own GDI/native surface as a PNG.
|
|||||||
**Errors:** `1005` on screenshot failure; `1001` if a supplied `target` is not
|
**Errors:** `1005` on screenshot failure; `1001` if a supplied `target` is not
|
||||||
found or ambiguous.
|
found or ambiguous.
|
||||||
|
|
||||||
**LIMITATION:** `screenshot.window` captures the window's **own GDI surface only**.
|
**How it works:** the window's on-screen rectangle is read back from the
|
||||||
It does **not** capture the OpenGL 3D viewport, and it may not capture some native
|
DWM-composited desktop framebuffer (`wxScreenDC`), so the capture includes every
|
||||||
child controls. To capture the 3D scene, use
|
native child control, the OpenGL 3D viewport, and ImGui overlays — it is a faithful
|
||||||
[`screenshot.viewport3d`](#screenshotviewport3d).
|
image of what the user sees. (Capturing the parent window's own client DC instead
|
||||||
|
would clip out child HWNDs and the GL surface, leaving them black; that is why this
|
||||||
|
method reads from the screen.)
|
||||||
|
|
||||||
|
**Caveats:**
|
||||||
|
|
||||||
|
- The window must be **visible and unobscured**. Because the source is the on-screen
|
||||||
|
framebuffer, any overlapping window occludes the captured region. The backend
|
||||||
|
raises the target window before capturing.
|
||||||
|
- **HiDPI:** the reported `width`/`height` come from the window's logical client size,
|
||||||
|
while the screen framebuffer is in physical pixels. On per-monitor-DPI displays the
|
||||||
|
two can differ; the capture may be cropped or scaled relative to the logical size.
|
||||||
|
- For a clean, occlusion-independent, arbitrary-resolution render of the 3D scene
|
||||||
|
(including when the 3D tab is not the visible view), use
|
||||||
|
[`screenshot.viewport3d`](#screenshotviewport3d) instead.
|
||||||
|
|
||||||
### `screenshot.viewport3d`
|
### `screenshot.viewport3d`
|
||||||
|
|
||||||
Render the active 3D plate offscreen and return it as a PNG. This is the correct
|
Render a 3D plate offscreen and return it as a PNG. Unlike `screenshot.window`, this
|
||||||
way to capture the 3D scene that `screenshot.window` cannot.
|
renders into an offscreen framebuffer, so it is independent of window size and
|
||||||
|
occlusion, works even when the 3D tab is hidden, and supports arbitrary output
|
||||||
|
resolution — making it the right choice for clean, deterministic captures.
|
||||||
|
|
||||||
**Params (all optional):**
|
**Params (all optional):**
|
||||||
|
|
||||||
@@ -475,6 +491,11 @@ Consequences and conventions:
|
|||||||
- **Input is asynchronous.** Do **not** rely on fixed sleeps. Use
|
- **Input is asynchronous.** Do **not** rely on fixed sleeps. Use
|
||||||
[`sync.wait_for`](#syncwait_for) — for example, wait for `btn_export` to become
|
[`sync.wait_for`](#syncwait_for) — for example, wait for `btn_export` to become
|
||||||
`enabled` after slicing completes — rather than sleeping for a guessed duration.
|
`enabled` after slicing completes — rather than sleeping for a guessed duration.
|
||||||
|
- **`screenshot.window` reads the screen.** It captures the on-screen, DWM-composited
|
||||||
|
framebuffer, so the target window must be visible and unobscured, and the result is
|
||||||
|
in physical pixels (see HiDPI caveat under [`screenshot.window`](#screenshotwindow)).
|
||||||
|
For occlusion-independent 3D captures use
|
||||||
|
[`screenshot.viewport3d`](#screenshotviewport3d).
|
||||||
- **Single-client / serialized.** v1 handles one request at a time; issue requests
|
- **Single-client / serialized.** v1 handles one request at a time; issue requests
|
||||||
sequentially from a single client.
|
sequentially from a single client.
|
||||||
- **GUI-thread marshaling.** Every backend call is marshaled onto the GUI thread
|
- **GUI-thread marshaling.** Every backend call is marshaled onto the GUI thread
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
#include <wx/choice.h>
|
#include <wx/choice.h>
|
||||||
#include <wx/checkbox.h>
|
#include <wx/checkbox.h>
|
||||||
#include <wx/uiaction.h> // wxUIActionSimulator (synthetic mouse/keyboard)
|
#include <wx/uiaction.h> // wxUIActionSimulator (synthetic mouse/keyboard)
|
||||||
#include <wx/dcclient.h> // wxClientDC
|
#include <wx/dcscreen.h> // wxScreenDC
|
||||||
#include <wx/dcmemory.h> // wxMemoryDC
|
#include <wx/dcmemory.h> // wxMemoryDC
|
||||||
#include <wx/mstream.h> // wxMemoryOutputStream
|
#include <wx/mstream.h> // wxMemoryOutputStream
|
||||||
|
|
||||||
@@ -296,10 +296,24 @@ PngImage WxUiBackend::screenshot_window(const UiNode* target) {
|
|||||||
const wxSize sz = win->GetClientSize();
|
const wxSize sz = win->GetClientSize();
|
||||||
if (sz.x <= 0 || sz.y <= 0)
|
if (sz.x <= 0 || sz.y <= 0)
|
||||||
throw AutomationError(kErrScreenshotFail, "window has no client area");
|
throw AutomationError(kErrScreenshotFail, "window has no client area");
|
||||||
|
// Capture from the on-screen (DWM-composited) framebuffer rather than the
|
||||||
|
// window's own client DC. A parent client DC clips out child HWNDs, so all of
|
||||||
|
// OrcaSlicer's custom child-window controls (sidebar buttons/combos/panels) and
|
||||||
|
// the GL canvas come back as uninitialized (black) bitmap memory. wxScreenDC
|
||||||
|
// reads the composited desktop, which includes every child window, the OpenGL
|
||||||
|
// surface, and ImGui overlays — pixel-perfect to what the user sees.
|
||||||
|
//
|
||||||
|
// Requirement: the window must be visible and unobscured (see doc/automation.md
|
||||||
|
// §platform caveats); the backend raises it before injecting input anyway.
|
||||||
|
// HiDPI note: GetClientSize is in logical units while wxScreenDC is in physical
|
||||||
|
// pixels; on per-monitor-DPI setups the captured size may differ from the logical
|
||||||
|
// client size (documented caveat, acceptable for v1).
|
||||||
|
win->Raise();
|
||||||
|
const wxPoint origin = win->ClientToScreen(wxPoint(0, 0));
|
||||||
wxBitmap bmp(sz.x, sz.y);
|
wxBitmap bmp(sz.x, sz.y);
|
||||||
wxClientDC dc(win);
|
wxScreenDC sdc;
|
||||||
wxMemoryDC mdc(bmp);
|
wxMemoryDC mdc(bmp);
|
||||||
mdc.Blit(0, 0, sz.x, sz.y, &dc, 0, 0);
|
mdc.Blit(0, 0, sz.x, sz.y, &sdc, origin.x, origin.y);
|
||||||
mdc.SelectObject(wxNullBitmap);
|
mdc.SelectObject(wxNullBitmap);
|
||||||
return wximage_to_png(bmp.ConvertToImage());
|
return wximage_to_png(bmp.ConvertToImage());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def main() -> int:
|
|||||||
with open("window.png", "wb") as f:
|
with open("window.png", "wb") as f:
|
||||||
f.write(orca.screenshot())
|
f.write(orca.screenshot())
|
||||||
with open("preview_3d.png", "wb") as f:
|
with open("preview_3d.png", "wb") as f:
|
||||||
f.write(orca.screenshot_3d(width=1024, height=768))
|
f.write(orca.screenshot_3d(width=1920, height=1080))
|
||||||
print("wrote window.png and preview_3d.png")
|
print("wrote window.png and preview_3d.png")
|
||||||
return 0
|
return 0
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user