Skip to content

refactor(mcp): extract TransientImportSession; restore selection in all transient-import tools#572

Merged
fernandotonon merged 2 commits into
masterfrom
refactor/mcp-transient-import-session
May 17, 2026
Merged

refactor(mcp): extract TransientImportSession; restore selection in all transient-import tools#572
fernandotonon merged 2 commits into
masterfrom
refactor/mcp-transient-import-session

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented May 17, 2026

Three MCP tools — `apply_atlas`, `bake_vat`, `list_morph_targets` — needed the same boilerplate around `MeshImporterExporter::importer`:

  • Snapshot the pre-import entity set + the user's selection.
  • Run the importer.
  • Diff to find the new entities.
  • On scope exit: destroy the imported scene nodes AND restore the prior selection.

PR #571 fixed the selection-restore hole in `list_morph_targets` only. `bake_vat` and `apply_atlas` have the same bug: `MeshImporterExporter::importer` auto-selects the new entities, so the destructor's `destroySceneNode` calls leave the `SelectionSet` holding dangling pointers. Any subsequent selection-touching code dereferences freed memory.

What this PR does

Extract the whole pattern into a `TransientImportSession` RAII helper (anonymous namespace, MCPServer.cpp) and migrate all three tools.

Each tool's body drops from ~50 lines of boilerplate to:
```cpp
TransientImportSession session(mgr);
if (QString err = session.runImporter(filePath); !err.isEmpty())
return makeErrorResult(err);
const auto& imported = session.importedEntities();
```

The helper:

  • Filters `getEntities()` by `getMovableType() == "Entity"` — gizmos / brush rings / mask overlays would otherwise crash on the raw cast.
  • Uses `SelectionSet::clearList()` (not `clear()`) in the destructor — the importer-added selection entries point at entities we're about to destroy; `clear()` would dereference freed memory while bookkeeping.

Net −32 lines across the three tools after the extraction.

Out of scope

`toolOptimizeMesh` is NOT migrated. It passes an extra `animOnlySkeletons` out-parameter to `MeshImporterExporter::importer` which `runImporter()` doesn't yet support. Will land as a separate small extension when we touch optimize-mesh next.

Test plan

  • CI: all existing tests pass; build clean on Linux/macOS/Windows.
  • Manual: `qtmesh vat` still works (uses CLI path; just confirms linker).
  • Manual via MCP: `apply_atlas` / `bake_vat` / `list_morph_targets` against a scene with an existing selection — selection should be unchanged after the tool returns.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Refactored internal mesh import tool implementations to use a unified import session system. This optimization enhances consistency and reliability across all import operations (including atlas application, VAT baking, and morph target processing), streamlines the import pipeline code, and reduces maintenance overhead. All existing functionality remains fully compatible.

Review Change Stack

…ll transient-import tools

Three MCP tools (`apply_atlas`, `bake_vat`, `list_morph_targets`)
needed the same pattern:
- Snapshot the pre-import entity set + the user's selection.
- Run `MeshImporterExporter::importer(...)`.
- Diff to find the imported entities.
- On scope exit: destroy the imported scene nodes AND restore the
  prior selection.

PR #571 fixed the selection-restore hole in `list_morph_targets`
only; `bake_vat` and `apply_atlas` had the same bug
(`MeshImporterExporter::importer` auto-selects the new entities;
my cleanup destroyed them, leaving the SelectionSet holding
dangling pointers).

Extract the whole pattern into a `TransientImportSession` RAII
helper (anonymous namespace, MCPServer.cpp) and migrate the three
tools to use it. Each tool's body drops from ~50 lines of
boilerplate to:

```cpp
TransientImportSession session(mgr);
if (QString err = session.runImporter(filePath); !err.isEmpty())
    return makeErrorResult(err);
const auto& imported = session.importedEntities();
```

The helper:
- Filters `getEntities()` by `getMovableType() == "Entity"` —
  gizmos / brush rings / mask overlays would otherwise crash on
  the raw cast.
- Uses `SelectionSet::clearList()` (not `clear()`) in the
  destructor — the importer-added selection entries point at
  entities we're about to destroy; `clear()` would dereference
  freed memory while bookkeeping.

`toolOptimizeMesh` is intentionally NOT migrated in this PR — it
passes an extra `animOnlySkeletons` out-parameter to
`MeshImporterExporter::importer`, which the helper's
`runImporter()` doesn't yet support. That's a separate small
extension when we touch optimize-mesh next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Warning

Rate limit exceeded

@fernandotonon has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 47 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81b75a0f-0f65-4aa8-902a-b07bd9802247

📥 Commits

Reviewing files that changed from the base of the PR and between 348babe and c06177e.

📒 Files selected for processing (1)
  • src/MCPServer.cpp
📝 Walkthrough

Walkthrough

A new TransientImportSession RAII helper consolidates mesh import bookkeeping—snapshotting entities and selections, running imports, identifying new entities, and restoring state on destruction. Three MCP tools (apply_atlas, bake_vat, list_morph_targets) are refactored to use this helper instead of duplicating the same manual snapshot-diff-cleanup pattern.

Changes

Mesh Import Transient Session

Layer / File(s) Summary
TransientImportSession RAII helper
src/MCPServer.cpp
New anonymous-namespace class wraps mesh import execution: snapshots pre-import Ogre::Entity objects and SelectionSet selections, runs the importer, tracks newly imported entities via scene-node scanning, and on destruction destroys imported scene-node parents while restoring the prior selection state.
MCP tools refactored to use TransientImportSession
src/MCPServer.cpp
apply_atlas, bake_vat, and list_morph_targets replace hand-written snapshot/diff/cleanup logic with the new helper: each calls session.runImporter(...), reads session.importedEntities(), validates the result, and lets the session handle cleanup and selection restoration automatically on scope exit.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A helper born to catch the fleeting mesh,
Snapshots before, restores after—the perfect fetch!
Three tools once tangled, now neat and tidy,
RAII cleans up—no more messy hide-y!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main refactoring objective: extracting a TransientImportSession helper and ensuring selection restoration across all transient-import tools.
Description check ✅ Passed The description comprehensively covers the motivation, technical implementation details, and testing plan, exceeding the template's basic requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/mcp-transient-import-session

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/MCPServer.cpp`:
- Around line 441-455: runImporter currently returns early when
MeshImporterExporter::importer throws, so newly created entities are not
recorded and remain in the live scene; modify the exception handling in
runImporter (around the MeshImporterExporter::importer call) to call
collectEntitiesSafe() and append any entities not present in m_beforeSet to
m_imported before returning from both the std::exception and catch-all handlers,
ensuring the same post-import bookkeeping (the loop using collectEntitiesSafe(),
m_beforeSet and m_imported) runs on the error path as well as the success path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 903e3183-2481-41f3-a812-e3ed0fd07e4d

📥 Commits

Reviewing files that changed from the base of the PR and between 27883bb and 348babe.

📒 Files selected for processing (1)
  • src/MCPServer.cpp

Comment thread src/MCPServer.cpp
CodeRabbit major on PR #572:

If `MeshImporterExporter::importer()` throws partway through, the
prior `runImporter()` returned the error without recording what
the importer managed to create — leaving partial state (scene
nodes, entities) in the live scene that the destructor never
cleaned up. That broke the transient-import contract on the
error path.

Fix: extract the post-import "diff `getEntities()` against
`m_beforeSet`" logic into `captureImportedEntities()` and call
it from all three exit paths (success + both catch blocks). The
helper dedups against `m_imported` so callers running it twice
is safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit 1b43207 into master May 17, 2026
20 checks passed
@fernandotonon fernandotonon deleted the refactor/mcp-transient-import-session branch May 17, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant