mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-12 15:03:33 +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`
|
||||
|
||||
Capture a window's own GDI/native surface as a PNG.
|
||||
Capture a window as a PNG, exactly as it appears on screen.
|
||||
|
||||
**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
|
||||
found or ambiguous.
|
||||
|
||||
**LIMITATION:** `screenshot.window` captures the window's **own GDI surface only**.
|
||||
It does **not** capture the OpenGL 3D viewport, and it may not capture some native
|
||||
child controls. To capture the 3D scene, use
|
||||
[`screenshot.viewport3d`](#screenshotviewport3d).
|
||||
**How it works:** the window's on-screen rectangle is read back from the
|
||||
DWM-composited desktop framebuffer (`wxScreenDC`), so the capture includes every
|
||||
native child control, the OpenGL 3D viewport, and ImGui overlays — it is a faithful
|
||||
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`
|
||||
|
||||
Render the active 3D plate offscreen and return it as a PNG. This is the correct
|
||||
way to capture the 3D scene that `screenshot.window` cannot.
|
||||
Render a 3D plate offscreen and return it as a PNG. Unlike `screenshot.window`, this
|
||||
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):**
|
||||
|
||||
@@ -475,6 +491,11 @@ Consequences and conventions:
|
||||
- **Input is asynchronous.** Do **not** rely on fixed sleeps. Use
|
||||
[`sync.wait_for`](#syncwait_for) — for example, wait for `btn_export` to become
|
||||
`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
|
||||
sequentially from a single client.
|
||||
- **GUI-thread marshaling.** Every backend call is marshaled onto the GUI thread
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <wx/choice.h>
|
||||
#include <wx/checkbox.h>
|
||||
#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/mstream.h> // wxMemoryOutputStream
|
||||
|
||||
@@ -296,10 +296,24 @@ PngImage WxUiBackend::screenshot_window(const UiNode* target) {
|
||||
const wxSize sz = win->GetClientSize();
|
||||
if (sz.x <= 0 || sz.y <= 0)
|
||||
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);
|
||||
wxClientDC dc(win);
|
||||
wxScreenDC sdc;
|
||||
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);
|
||||
return wximage_to_png(bmp.ConvertToImage());
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ def main() -> int:
|
||||
with open("window.png", "wb") as f:
|
||||
f.write(orca.screenshot())
|
||||
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")
|
||||
return 0
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user