feat(pose-lib): save_pose_library / load_pose_library MCP + CLI#604
Conversation
Closes the GUI / MCP / CLI triad for the pose library now that D-Project (#602) provides the .poselib sidecar format. ## What ships ### MCP tools (2 new) - `save_pose_library` — required `path`. Wraps `PoseLibrary::savePoseLibraryForSelection`. Returns ok+path on success; error on no selection / empty library / unwritable path. - `load_pose_library` — required `path`. Wraps `loadPoseLibraryForSelection`. Returns ok+path+count on success; error on no selection / missing file / malformed JSON or schema (in-memory library is preserved on parse failure thanks to the D-Project staging fix). ### CLI mode (new branch in `cmdPose`) `qtmesh pose <library.poselib> --library list [--json]` — reads the sidecar JSON directly (no mesh load) and prints the pose names. JSON shape mirrors the other CLI tools: `{ file, count, poses: [name…] }`. Doesn't go through `PoseLibrary` itself — there's no entity to key against in a standalone CLI invocation. Just parses the JSON. This intentionally bypasses the strict schema-replacement semantics that `loadPoseLibrary` enforces on a live entity; for read-only listing, schema-checking the file before walking is enough. `--library apply <name> -o out.fbx` is deliberately not in this PR — it needs the round-trip exporter to write pose-driven bone states back into the mesh. Documented in the help text as a follow-up. ## 3 new MCP tests - `SavePoseLibrary_MissingPathRejected` — empty args → error mentions 'path'. - `LoadPoseLibrary_MissingPathRejected` — same for load. - `LoadPoseLibrary_MissingFileRejected` — nonexistent file → error. ## Manual CLI verification - `qtmesh pose test.poselib --library list` → "Poses (N): …" - `qtmesh pose test.poselib --library list --json` → JSON shape. - `qtmesh pose /nonexistent.poselib --library list` → exit 1 with "File not found:". ## #521 status | Sub-slice | Status | |-|-| | D1 — Singleton data layer | shipped (#592) | | D-MCP — 5 + 2 = 7 tools | shipped (#593, #599, **#604**) | | D3 — Undo commands | shipped (#595) | | D4 — Mirror pose | shipped (#597) | | D-Project — .poselib sidecar | shipped (#602) | | D-CLI — `qtmesh pose --library list` | shipped (**this PR**) | | D2 — Inspector subgroup | follow-up | | D5 — Apply-with-mask | follow-up | | D6 — Time-blended apply | follow-up | | D-Thumbnail | follow-up | | D-CLI apply | follow-up (needs exporter round-trip) | Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc9b6d9e38
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| err() << "Error: schema mismatch in " << filePath << Qt::endl; | ||
| return 1; | ||
| } | ||
| const QJsonArray poses = root.value("poses").toArray(); |
There was a problem hiding this comment.
Reject invalid pose library payload before listing
In --library list mode, the parser only checks the schema string and then does root.value("poses").toArray(), which silently yields an empty array when poses is missing or not an array. That makes malformed .poselib files report success as "No poses in library" instead of failing, which can hide file corruption and produce incorrect automation results when callers rely on a non-zero exit code for invalid libraries.
Useful? React with 👍 / 👎.
|
Codex P2 on PR #604 (merged). The CLI's `--library list` path checked the `schema` string but then did `root.value("poses").toArray()` unconditionally — same silent-empty-on-bad-type bug class as loadPoseLibrary had on PR #602 (fixed in #603), just in the duplicated parse-in-the-CLI path. A schema-matching `.poselib` file with `poses` missing or non-array would report "No poses in library" with exit 0 instead of surfacing the corruption. CI/audit workflows relying on non-zero exit for invalid libraries would miss the failure. Fix: explicit `isArray()` check after the schema match, emit "malformed 'poses' field in <path>" to stderr and return 1. Manual verification: a `{"schema":"qtmesheditor.poselib.v1","poses":"oops"}` file now exits 1 with the expected message instead of 0 with a vacuous result. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ply <name> -o <out> (#606) Adds the apply side of `--library` mode (the list side shipped in #604). Loads a mesh, loads a .poselib sidecar, applies the named pose to the skeleton, exports the posed mesh. ## CLI shape qtmesh pose <mesh.fbx> --library apply --lib <library.poselib> --apply <name> -o <out.fbx> `filePath` (positional) is the MESH in apply mode, contrasting with list mode where it's the library file. The library path arrives via `--lib` so the positional convention stays consistent across all `pose` modes (positional = primary input). ## Implementation - Reuses `MeshImporterExporter::importer` + `exportCurrentPose` — same path the `--animation --time` mode takes, just with the pose source being `PoseLibrary::applyPose` instead of an `AnimationState`. - Strict input validation: - mesh file exists - library file exists - mesh has a skeleton (apply needs one) - pose name exists in the library (otherwise lists available names on stderr so the user can fix their command line) - Sentry breadcrumbs `cli.pose` + `file.import` for telemetry. Help text updated next to the existing `--library list` line. No new tests in this PR — the existing PoseLibrary + CLI tests cover the loadPoseLibrary / applyPose paths in isolation, and the new code is just glue between them. ## #521 status | Sub-slice | Status | |-|-| | D1 — Singleton data layer | shipped (#592) | | D-MCP — 7 tools | shipped (#593, #599, #604) | | D3 — Undo commands | shipped (#595) | | D4 — Mirror pose | shipped (#597) | | D-Project — .poselib sidecar | shipped (#602) | | D-CLI — `pose --library list` | shipped (#604) | | D-CLI — `pose --library apply` | **this PR** | | D2 — Inspector subgroup | follow-up | | D5 — Apply-with-mask | follow-up | | D6 — Time-blended apply | follow-up | | D-Thumbnail | follow-up | Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>



Closes the GUI / MCP / CLI triad for the pose library now that D-Project (#602) provides the
.poselibsidecar format.What ships
MCP tools (2 new)
save_pose_library— requiredpath. WrapsPoseLibrary::savePoseLibraryForSelection. Returnsok+pathon success; error on no selection / empty library / unwritable path.load_pose_library— requiredpath. WrapsloadPoseLibraryForSelection. Returnsok+path+counton success; error on no selection / missing file / malformed JSON or schema (in-memory library is preserved on parse failure thanks to the D-Project staging fix).CLI mode (new branch in
cmdPose)qtmesh pose <library.poselib> --library list [--json]— reads the sidecar JSON directly (no mesh load needed) and prints the pose names. JSON shape mirrors the other CLI tools:{ file, count, poses: [name…] }.Doesn't go through
PoseLibraryitself — there's no entity to key against in a standalone CLI invocation; we just parse the JSON. This intentionally bypasses the strict schema-replacement semantics thatloadPoseLibraryenforces on a live entity; for read-only listing, schema-checking the file is enough.--library apply <name> -o out.fbxis deliberately not in this PR — it needs the round-trip exporter to write pose-driven bone states back into the mesh.3 new MCP tests + manual CLI verification
SavePoseLibrary_MissingPathRejectedLoadPoseLibrary_MissingPathRejectedLoadPoseLibrary_MissingFileRejectedManual:
qtmesh pose test.poselib --library list→"Poses (N): …"qtmesh pose test.poselib --library list --json→ JSON shapeqtmesh pose /nonexistent.poselib --library list→ exit 1#521 status
qtmesh pose --library list🤖 Generated with Claude Code