Simulate strokes from pano cli

This commit is contained in:
2026-06-01 09:12:55 +02:00
parent d0ef88be89
commit dc252b2f24
4 changed files with 106 additions and 3 deletions

View File

@@ -98,6 +98,8 @@ Known local toolchain state:
`pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture. `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture.
- `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and - `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and
is covered by `pano_cli_create_animation_document_smoke`. is covered by `pano_cli_create_animation_document_smoke`.
- `pano_cli simulate-stroke` exposes the pure stroke sampler for scripted
automation and is covered by `pano_cli_simulate_stroke_smoke`.
- `panopainter_validate_shaders` validates the current combined GLSL shader - `panopainter_validate_shaders` validates the current combined GLSL shader
files for one vertex stage marker, one fragment stage marker, valid marker files for one vertex stage marker, one fragment stage marker, valid marker
order, and existing relative includes. order, and existing relative includes.

View File

@@ -328,9 +328,10 @@ length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid
input tests. input tests.
`pano_cli inspect-image` exposes PNG IHDR metadata as JSON, and `pano_cli inspect-image` exposes PNG IHDR metadata as JSON, and
`pano_cli create-document` can create simple animation documents with explicit `pano_cli create-document` can create simple animation documents with explicit
frame count/duration. `pano_cli parse-layout` exercises the XML layout path. frame count/duration, and `pano_cli simulate-stroke` exercises the pure stroke
Continue expanding document behavior toward legacy Canvas parity and then port sampler for scripted-stroke automation. `pano_cli parse-layout` exercises the
OpenGL classes behind the renderer boundary. XML layout path. Continue expanding document behavior toward legacy Canvas
parity and then port OpenGL classes behind the renderer boundary.
Implementation tasks: Implementation tasks:
@@ -582,6 +583,8 @@ Results:
- `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON - `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON
for the tiny IHDR fixture. for the tiny IHDR fixture.
- `pano_cli_parse_layout_smoke` passed. - `pano_cli_parse_layout_smoke` passed.
- `pano_cli_simulate_stroke_smoke` passed and reports deterministic stroke
sample counts/distances.
- `panopainter_validate_shaders` passed, validating 25 shader programs and 7 - `panopainter_validate_shaders` passed, validating 25 shader programs and 7
shader includes for stage markers and include graph integrity. shader includes for stage markers and include graph integrity.
- PowerShell analyze automation returns JSON summaries and includes the shader - PowerShell analyze automation returns JSON summaries and includes the shader

View File

@@ -224,4 +224,10 @@ if(TARGET pano_cli)
COMMAND pano_cli parse-layout --path "${CMAKE_CURRENT_SOURCE_DIR}/data/layouts/simple-layout.xml") COMMAND pano_cli parse-layout --path "${CMAKE_CURRENT_SOURCE_DIR}/data/layouts/simple-layout.xml")
set_tests_properties(pano_cli_parse_layout_smoke PROPERTIES set_tests_properties(pano_cli_parse_layout_smoke PROPERTIES
LABELS "ui;integration;desktop-fast") LABELS "ui;integration;desktop-fast")
add_test(NAME pano_cli_simulate_stroke_smoke
COMMAND pano_cli simulate-stroke --x1 0 --y1 0 --x2 10 --y2 0 --spacing 2)
set_tests_properties(pano_cli_simulate_stroke_smoke PROPERTIES
LABELS "paint;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"samples\":6.*\"distance\":10")
endif() endif()

View File

@@ -4,6 +4,7 @@
#include "document/document.h" #include "document/document.h"
#include "foundation/parse.h" #include "foundation/parse.h"
#include "foundation/result.h" #include "foundation/result.h"
#include "paint/stroke.h"
#include "ui_core/layout_xml.h" #include "ui_core/layout_xml.h"
#include <cstdint> #include <cstdint>
@@ -37,6 +38,14 @@ struct InspectProjectArgs {
std::string path; std::string path;
}; };
struct SimulateStrokeArgs {
std::uint32_t x1 = 0;
std::uint32_t y1 = 0;
std::uint32_t x2 = 0;
std::uint32_t y2 = 0;
std::uint32_t spacing = 1;
};
void print_error(std::string_view command, std::string_view message) void print_error(std::string_view command, std::string_view message)
{ {
std::cout << "{\"ok\":false,\"command\":\"" << command std::cout << "{\"ok\":false,\"command\":\"" << command
@@ -51,6 +60,7 @@ void print_help()
<< " inspect-image --path FILE\n" << " inspect-image --path FILE\n"
<< " inspect-project --path FILE\n" << " inspect-project --path FILE\n"
<< " parse-layout --path FILE\n" << " parse-layout --path FILE\n"
<< " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n"
<< " --help\n"; << " --help\n";
} }
@@ -291,6 +301,84 @@ int inspect_project(int argc, char** argv)
return 0; return 0;
} }
pp::foundation::Status parse_simulate_stroke_args(int argc, char** argv, SimulateStrokeArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--x1" || key == "--y1" || key == "--x2" || key == "--y2" || key == "--spacing") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
const auto value = pp::foundation::parse_u32(argv[++i]);
if (!value) {
return value.status();
}
if (key == "--x1") {
args.x1 = value.value();
} else if (key == "--y1") {
args.y1 = value.value();
} else if (key == "--x2") {
args.x2 = value.value();
} else if (key == "--y2") {
args.y2 = value.value();
} else {
args.spacing = value.value();
}
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
if (args.spacing == 0) {
return pp::foundation::Status::invalid_argument("stroke spacing must be greater than zero");
}
return pp::foundation::Status::success();
}
int simulate_stroke(int argc, char** argv)
{
SimulateStrokeArgs args;
const auto status = parse_simulate_stroke_args(argc, argv, args);
if (!status.ok()) {
print_error("simulate-stroke", status.message);
return 2;
}
const pp::paint::StrokePoint points[] {
pp::paint::StrokePoint {
.x = static_cast<float>(args.x1),
.y = static_cast<float>(args.y1),
.pressure = 1.0F,
},
pp::paint::StrokePoint {
.x = static_cast<float>(args.x2),
.y = static_cast<float>(args.y2),
.pressure = 1.0F,
},
};
const auto samples = pp::paint::sample_stroke(
points,
pp::paint::StrokeSamplingConfig {
.spacing = static_cast<float>(args.spacing),
});
if (!samples) {
print_error("simulate-stroke", samples.status().message);
return 2;
}
const auto& first = samples.value().front();
const auto& last = samples.value().back();
std::cout << "{\"ok\":true,\"command\":\"simulate-stroke\""
<< ",\"samples\":" << samples.value().size()
<< ",\"first\":{\"x\":" << first.x << ",\"y\":" << first.y << "}"
<< ",\"last\":{\"x\":" << last.x << ",\"y\":" << last.y
<< ",\"distance\":" << last.distance << "}}\n";
return 0;
}
pp::foundation::Status parse_layout_args(int argc, char** argv, ParseLayoutArgs& args) pp::foundation::Status parse_layout_args(int argc, char** argv, ParseLayoutArgs& args)
{ {
for (int i = 2; i < argc; ++i) { for (int i = 2; i < argc; ++i) {
@@ -372,6 +460,10 @@ int main(int argc, char** argv)
return inspect_project(argc, argv); return inspect_project(argc, argv);
} }
if (command == "simulate-stroke") {
return simulate_stroke(argc, argv);
}
if (command == "parse-layout") { if (command == "parse-layout") {
return parse_layout(argc, argv); return parse_layout(argc, argv);
} }