refactor(automation): drop screenshot.viewport3d, keep only screenshot.window

The on-screen window capture is composited from the desktop framebuffer, so it
already includes the GL 3D viewport as currently shown (model in the editor,
toolpaths in Preview). The offscreen render_thumbnail path only ever drew the
model GLVolumeCollection — never the gcode toolpaths — and produced a blank image
after slicing because the app switches to the Preview panel. Rather than maintain a
second, more limited capture method, remove it entirely.

Removes the JSON-RPC method, IUiBackend/WxUiBackend implementation, dispatcher
route + capability entry, the now-dead opt_int/thumbnail_to_wximage helpers and
ThumbnailData include, the mock override + unit test, and the Python
screenshot_3d client method. Docs updated accordingly.
This commit is contained in:
SoftFever
2026-06-03 18:05:23 +08:00
parent 952696fd1f
commit 892b33bac5
10 changed files with 25 additions and 129 deletions

View File

@@ -92,11 +92,9 @@ public:
// Send key chords (e.g. ctrl+s) to the focused window.
virtual bool send_keys(const std::vector<KeyChord>& chords) = 0;
// Screenshots. target == nullptr => main frame.
// Screenshot. target == nullptr => main frame. Captured from the on-screen
// composited framebuffer, so it includes the GL viewport and ImGui overlays.
virtual PngImage screenshot_window(const UiNode* target) = 0;
virtual PngImage screenshot_viewport3d(std::optional<int> plate,
std::optional<int> width,
std::optional<int> height) = 0;
};
}}} // namespace Slic3r::GUI::Automation

View File

@@ -155,12 +155,6 @@ std::string base64_encode(const std::vector<unsigned char>& data) {
return out;
}
std::optional<int> opt_int(const nlohmann::json& p, const char* key) {
if (p.is_object() && p.contains(key) && p.at(key).is_number_integer())
return p.at(key).get<int>();
return std::nullopt;
}
nlohmann::json image_to_json(const PngImage& img) {
if (img.png.empty())
throw AutomationError(kErrScreenshotFail, "screenshot produced no data");
@@ -174,8 +168,7 @@ nlohmann::json JsonRpcDispatcher::m_version(const nlohmann::json&) {
{"protocol", "2.0"},
{"capabilities", nlohmann::json::array({
"tree.dump","tree.find","widget.get","input.click","input.type",
"input.key","sync.wait_for","app.state","screenshot.window",
"screenshot.viewport3d" })} };
"input.key","sync.wait_for","app.state","screenshot.window" })} };
}
nlohmann::json JsonRpcDispatcher::dispatch(const nlohmann::json& request) {
@@ -200,7 +193,6 @@ nlohmann::json JsonRpcDispatcher::dispatch(const nlohmann::json& request) {
if (method == "sync.wait_for") return make_result(id, m_sync_wait_for(params));
if (method == "app.state") return make_result(id, m_app_state(params));
if (method == "screenshot.window") return make_result(id, m_screenshot_window(params));
if (method == "screenshot.viewport3d") return make_result(id, m_screenshot_viewport3d(params));
return make_error(id, kMethodNotFound, "unknown method: " + method);
} catch (const AutomationError& e) {
return make_error(id, e.code, e.what());
@@ -350,9 +342,4 @@ nlohmann::json JsonRpcDispatcher::m_screenshot_window(const nlohmann::json& para
return image_to_json(m_backend.screenshot_window(target_ptr));
}
nlohmann::json JsonRpcDispatcher::m_screenshot_viewport3d(const nlohmann::json& params) {
return image_to_json(m_backend.screenshot_viewport3d(
opt_int(params, "plate"), opt_int(params, "width"), opt_int(params, "height")));
}
}}} // namespace

View File

@@ -47,7 +47,6 @@ private:
nlohmann::json m_sync_wait_for(const nlohmann::json& params);
nlohmann::json m_app_state(const nlohmann::json& params);
nlohmann::json m_screenshot_window(const nlohmann::json& params);
nlohmann::json m_screenshot_viewport3d(const nlohmann::json& params);
// Resolve a unique, actionable (enabled+visible) node from params["target"].
// Throws kErrNotFound (missing/ambiguous) or kErrNotActionable (disabled/hidden).

View File

@@ -5,9 +5,8 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp" // get_current_canvas3D() for app.state
#include "libslic3r/Model.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include <wx/window.h>
#include <wx/toplevel.h>
@@ -270,21 +269,6 @@ PngImage wximage_to_png(const wxImage& image) {
return out;
}
// RGBA ThumbnailData -> wxImage (mirrors GLCanvas3D::debug_output_thumbnail —
// note the vertical flip GL rows require).
wxImage thumbnail_to_wximage(const ThumbnailData& td) {
wxImage image((int)td.width, (int)td.height);
image.InitAlpha();
for (unsigned int r = 0; r < td.height; ++r) {
unsigned int rr = (td.height - 1 - r) * td.width;
for (unsigned int c = 0; c < td.width; ++c) {
const unsigned char* px = td.pixels.data() + 4 * (rr + c);
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
image.SetAlpha((int)c, (int)r, px[3]);
}
}
return image;
}
} // namespace
PngImage WxUiBackend::screenshot_window(const UiNode* target) {
@@ -319,32 +303,4 @@ PngImage WxUiBackend::screenshot_window(const UiNode* target) {
});
}
PngImage WxUiBackend::screenshot_viewport3d(std::optional<int> plate,
std::optional<int> width,
std::optional<int> height) {
return run_on_gui(m_gui_timeout_ms, [&]() -> PngImage {
Plater* p = wxGetApp().plater();
if (p == nullptr)
throw AutomationError(kErrScreenshotFail, "no plater");
GLCanvas3D* canvas = p->get_current_canvas3D();
if (canvas == nullptr)
throw AutomationError(kErrScreenshotFail, "no 3D canvas");
const unsigned int w = width ? (unsigned)*width : 800u;
const unsigned int h = height ? (unsigned)*height : 600u;
// Render the active plate's 3D scene into an offscreen RGBA buffer.
// render_thumbnail makes the canvas's GL context current itself. The
// pixel size is governed by w/h; `sizes` stays empty as elsewhere.
// Fields: {sizes, printable_only, parts_only, show_bed, transparent_background, plate_id}.
const int plate_id = plate ? *plate : 0; // v1: default active plate
const ThumbnailsParams params{ {}, false, false, true, false, plate_id };
ThumbnailData data;
canvas->render_thumbnail(data, w, h, params, Camera::EType::Ortho);
if (!data.is_valid())
throw AutomationError(kErrScreenshotFail, "thumbnail render failed");
return wximage_to_png(thumbnail_to_wximage(data));
});
}
}}} // namespace Slic3r::GUI::Automation

View File

@@ -19,8 +19,6 @@ public:
bool type_text(const std::string& text) override;
bool send_keys(const std::vector<KeyChord>& chords) override;
PngImage screenshot_window(const UiNode* target) override;
PngImage screenshot_viewport3d(std::optional<int> plate, std::optional<int> width,
std::optional<int> height) override;
private:
int m_gui_timeout_ms;