diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 0422419..9fb47da 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -98,6 +98,8 @@ Known local toolchain state: `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture. - `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and 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 files for one vertex stage marker, one fragment stage marker, valid marker order, and existing relative includes. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 3acc921..e3b9858 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -328,9 +328,10 @@ length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid input tests. `pano_cli inspect-image` exposes PNG IHDR metadata as JSON, and `pano_cli create-document` can create simple animation documents with explicit -frame count/duration. `pano_cli parse-layout` exercises the XML layout path. -Continue expanding document behavior toward legacy Canvas parity and then port -OpenGL classes behind the renderer boundary. +frame count/duration, and `pano_cli simulate-stroke` exercises the pure stroke +sampler for scripted-stroke automation. `pano_cli parse-layout` exercises the +XML layout path. Continue expanding document behavior toward legacy Canvas +parity and then port OpenGL classes behind the renderer boundary. Implementation tasks: @@ -582,6 +583,8 @@ Results: - `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON for the tiny IHDR fixture. - `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 shader includes for stage markers and include graph integrity. - PowerShell analyze automation returns JSON summaries and includes the shader diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a107af..f7285f6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -224,4 +224,10 @@ if(TARGET pano_cli) COMMAND pano_cli parse-layout --path "${CMAKE_CURRENT_SOURCE_DIR}/data/layouts/simple-layout.xml") set_tests_properties(pano_cli_parse_layout_smoke PROPERTIES 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() diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 4c94ae9..c3ac4b2 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -4,6 +4,7 @@ #include "document/document.h" #include "foundation/parse.h" #include "foundation/result.h" +#include "paint/stroke.h" #include "ui_core/layout_xml.h" #include @@ -37,6 +38,14 @@ struct InspectProjectArgs { 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) { std::cout << "{\"ok\":false,\"command\":\"" << command @@ -51,6 +60,7 @@ void print_help() << " inspect-image --path FILE\n" << " inspect-project --path FILE\n" << " parse-layout --path FILE\n" + << " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n" << " --help\n"; } @@ -291,6 +301,84 @@ int inspect_project(int argc, char** argv) 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(args.x1), + .y = static_cast(args.y1), + .pressure = 1.0F, + }, + pp::paint::StrokePoint { + .x = static_cast(args.x2), + .y = static_cast(args.y2), + .pressure = 1.0F, + }, + }; + const auto samples = pp::paint::sample_stroke( + points, + pp::paint::StrokeSamplingConfig { + .spacing = static_cast(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) { for (int i = 2; i < argc; ++i) { @@ -372,6 +460,10 @@ int main(int argc, char** argv) return inspect_project(argc, argv); } + if (command == "simulate-stroke") { + return simulate_stroke(argc, argv); + } + if (command == "parse-layout") { return parse_layout(argc, argv); }