Skip to content

Add CLI pipeline (qtmesh) for headless mesh operations#178

Open
fernandotonon wants to merge 5 commits intomasterfrom
feature/cli-pipeline
Open

Add CLI pipeline (qtmesh) for headless mesh operations#178
fernandotonon wants to merge 5 commits intomasterfrom
feature/cli-pipeline

Conversation

@fernandotonon
Copy link
Owner

@fernandotonon fernandotonon commented Mar 8, 2026

Summary

  • New qtmesh CLI tool — headless command-line interface for 3D mesh operations, built alongside the GUI binary
  • Commands: info, fix, convert, anim (list/rename), merge-animations
  • Auto-detection: CLI mode activates via qtmesh symlink, --cli flag, or recognized subcommand as first argument
  • Build integration: qtmesh symlink (macOS/Linux) or qtmesh.cmd wrapper (Windows) created automatically at build time
  • Public AnimationMerger::renameAnimation() — exposed for CLI anim --rename (was previously a private free function)
  • Test noise suppression — Ogre log and Qt debug messages suppressed during test runs
  • Documentation — CLAUDE.md and README.md updated with full CLI usage examples

CLI Usage

qtmesh info model.fbx [--json]                        # inspect mesh
qtmesh convert model.fbx -o model.gltf2               # convert formats
qtmesh fix model.fbx -o fixed.fbx [--all]             # optimize mesh
qtmesh anim model.fbx --list [--json]                  # list animations
qtmesh anim model.fbx --rename "old" "new" [-o out]    # rename animation
qtmesh merge-animations --base a.fbx --animations b.fbx c.fbx --output merged.fbx

Files Changed

File Change
src/CLIPipeline.cpp/h New CLI pipeline implementation (info, fix, convert, anim, merge-animations)
src/CLIPipeline_test.cpp Unit tests for CLI pipeline
src/main.cpp CLI mode detection before QApplication creation
src/CMakeLists.txt Add CLIPipeline to build, create qtmesh symlink
src/AnimationMerger.cpp/h Make renameAnimation public static
src/Assimp/Importer.cpp/h Accept additional Assimp flags parameter
src/MeshImporterExporter.cpp/h Pass additional flags through to Assimp importer
src/test_main.cpp Suppress Ogre/Qt debug noise in tests
CLAUDE.md Document CLI pipeline architecture and commands
README.md Document CLI usage for end users

Test plan

  • Build succeeds on all platforms (macOS, Linux, Windows)
  • qtmesh --help prints usage
  • qtmesh --version prints version
  • qtmesh info <file> shows mesh info (text and JSON)
  • qtmesh convert <input> -o <output> converts between formats
  • qtmesh fix <input> -o <output> re-imports/exports with optimizations
  • qtmesh anim <file> --list lists animations
  • qtmesh anim <file> --rename <old> <new> renames animation
  • qtmesh merge-animations merges animation files
  • Unit tests pass (CLIPipeline_test.cpp)
  • GUI mode still works normally (no regression)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Full CLI pipeline for headless mesh processing: info, convert, fix, anim (list/rename/merge animations); invoke via qtmesh, --cli, or recognized subcommands. Added support for passing extra import flags to influence import behavior.
  • Documentation

    • Expanded README and website with CLI Pipeline usage, examples, verbose/help notes, and updated CLI examples.
  • Tests

    • Large suite of CLI and mesh tests covering formatting, extraction, fix/convert, and animation workflows.
  • Chores

    • Build now produces a qtmesh wrapper for CLI usage.

fernandotonon and others added 2 commits March 7, 2026 21:35
Simplify FixOptions to only expose --remove-degenerates and
--merge-materials as extra flags on top of the standard import pipeline
(which already applies join-vertices, smooth-normals, optimize, etc.).
Thread additionalFlags through MeshImporterExporter::importer() and
AssimpToOgreImporter::loadModel() so they get OR'd into the standard
Assimp post-processing flags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire CLIPipeline into main.cpp with automatic detection via qtmesh
symlink, --cli flag, or recognized subcommand. Create qtmesh symlink
at build time (macOS/Linux) and batch wrapper (Windows). Expose
AnimationMerger::renameAnimation as public API for CLI anim --rename.
Suppress Ogre/Qt debug noise in test output. Document all CLI commands
in CLAUDE.md and README.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Adds a headless CLI pipeline (qtmesh) with subcommands info, convert, fix, anim; wires CLI detection in main to CLIPipeline::run; threads Assimp additionalFlags through importers; moves animation rename into AnimationMerger; adds build wrappers, extensive tests, and documentation updates.

Changes

Cohort / File(s) Summary
CLI Pipeline Core
src/CLIPipeline.h, src/CLIPipeline.cpp, src/CLIPipeline_test.cpp
Introduce static CLIPipeline API and types (MeshInfo, FixOptions), implement run and subcommands (info, convert, fix, anim), headless Ogre init, stdout/stderr routing, mesh extraction/formatting, and unit/e2e tests.
Entry Point & Build
src/main.cpp, src/CMakeLists.txt
Detect CLI mode (exe name, --cli, or subcommand) and call CLIPipeline::run; add platform post-build wrappers/symlinks or wrapper script to expose qtmesh.
Animation Merger
src/AnimationMerger.h, src/AnimationMerger.cpp
Promote animation rename helper to AnimationMerger::renameAnimation (header added/updated) and update merge call sites.
Importer / Mesh Importer Exporter
src/Assimp/Importer.h, src/Assimp/Importer.cpp, src/MeshImporterExporter.h, src/MeshImporterExporter.cpp
Add optional unsigned int additionalFlags (default 0) to AssimpToOgreImporter::loadModel and MeshImporterExporter::importer, and pass these flags into Assimp read calls.
Tests & Logging
src/test_main.cpp, src/CLIPipeline_test.cpp, src/MergeAnimationsCLI_test.cpp, src/TestHelpers.h
Suppress noisy Qt/Ogre logs in tests, add extensive CLIPipeline tests, update CLI tests to use anim subcommand and new argument patterns, and update test helper comments.
Docs & Readme
CLAUDE.md, README.md, docs/index.html
Add CLI Pipeline documentation and examples for inspect/convert/fix/anim workflows; update README and site examples to reference qtmesh and new command forms.
Minor Call-site Updates
src/AnimationMerger.cpp, src/MeshImporterExporter.cpp, src/Assimp/Importer.cpp
Call-site and signature updates to use the new member/static method and propagate additionalFlags through import paths.

Sequence Diagram(s)

sequenceDiagram
    participant User as User CLI
    participant Main as main.cpp
    participant Pipeline as CLIPipeline
    participant QApp as hidden QApplication
    participant Ogre as Ogre Engine
    participant Importer as MeshImporterExporter/Assimp
    participant FS as File I/O
    participant Out as stdout/stderr

    User->>Main: launch executable with args
    Main->>Main: detect CLI mode (name / --cli / subcommand)
    alt CLI Mode
        Main->>Pipeline: CLIPipeline::run(argc, argv)
        Pipeline->>Pipeline: parse globals (--help,--version,--verbose)
        Pipeline->>QApp: create hidden QApplication
        Pipeline->>Out: redirect stdout/stderr (suppress engine noise)
        Pipeline->>Ogre: init headless Ogre context
        Pipeline->>Importer: load(path, convertLH, additionalFlags)
        Importer->>FS: ReadFile via Assimp
        Importer->>Ogre: produce Ogre::Mesh / Entity
        Pipeline->>Pipeline: perform subcommand (info/convert/fix/anim)
        Pipeline->>Out: emit text or JSON results
        Pipeline->>Main: return exit code
    else GUI Mode
        Main->>QApp: normal GUI startup
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through flags and files tonight,

Quiet Ogre hid beneath moonlight,
CLI whispers stitched bones and skin,
Anim names changed with a cheerful grin,
qtmesh hums — assets take flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a new CLI tool (qtmesh) for headless mesh operations, which aligns with the comprehensive CLI implementation in the PR.
Description check ✅ Passed The PR description is well-structured with summary, CLI usage examples, files changed table, and test plan, covering all required template sections adequately.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/cli-pipeline

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

Copy link

@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.

🧹 Nitpick comments (3)
src/test_main.cpp (1)

54-62: Minor: Clarify comment about createLog parameters.

The inline comment // suppressDebugOut, suppressFileOutput doesn't match Ogre's createLog signature which is (name, defaultLog, debuggerOutput, suppressFileOutput). The actual effect is correct (suppresses debugger output and file output), but the comment is misleading.

📝 Suggested fix
     // Suppress Ogre log output (debug spam from Root, RenderSystem, plugins).
     // Must be done before any Manager::getSingleton() call creates Root.
     if (!Ogre::LogManager::getSingletonPtr()) {
         auto* logMgr = new Ogre::LogManager();
-        logMgr->createLog("ogre.log", true, false, true); // suppressDebugOut, suppressFileOutput
+        logMgr->createLog("ogre.log", true, false, true); // defaultLog, debuggerOutput=false, suppressFileOutput
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test_main.cpp` around lines 54 - 62, The comment next to the
Ogre::LogManager::createLog call is misleading about its parameters; update the
inline comment near Ogre::LogManager::getSingletonPtr()/createLog("ogre.log",
true, false, true) to reflect the actual signature (name, defaultLog,
debuggerOutput, suppressFileOutput) and state that the call sets
defaultLog=true, disables debuggerOutput (false) and enables suppressFileOutput
(true), leaving qInstallMessageHandler(testMessageHandler) unchanged.
src/CMakeLists.txt (1)

279-299: Windows wrapper uses file(WRITE) at configure-time instead of POST_BUILD.

The Windows .cmd wrapper is created via file(WRITE) which executes at CMake configure time, unlike macOS/Linux which use add_custom_command(POST_BUILD). This creates an inconsistency:

  • The wrapper is created before the build starts
  • If CMAKE_PROJECT_NAME or CMAKE_RUNTIME_OUTPUT_DIRECTORY changes, the wrapper won't update until the next CMake reconfiguration

This is functionally acceptable since the wrapper content is static, but for consistency, consider using a POST_BUILD command on Windows as well.

♻️ Optional: Use POST_BUILD on Windows for consistency
 elseif(WIN32)
-    file(WRITE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtmesh.cmd"
-        "@echo off\n\"%~dp0${CMAKE_PROJECT_NAME}.exe\" --cli %*\n")
+    add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E echo "@echo off" > "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtmesh.cmd"
+        COMMAND ${CMAKE_COMMAND} -E echo "\"%%~dp0${CMAKE_PROJECT_NAME}.exe\" --cli %%*" >> "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtmesh.cmd"
+        COMMENT "Creating qtmesh.cmd wrapper"
+    )

Alternatively, use file(GENERATE) which evaluates generator expressions at generation time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CMakeLists.txt` around lines 279 - 299, The Windows wrapper is being
created with file(WRITE) at configure-time causing inconsistency with the
macOS/Linux add_custom_command(... POST_BUILD) flow; replace the configure-time
file(WRITE) for "qtmesh.cmd" with a POST_BUILD step (use
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND
${CMAKE_COMMAND} -E echo_append / file generation) that writes the same "@echo
off\n\"%~dp0${CMAKE_PROJECT_NAME}.exe\" --cli %*\n" into
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtmesh.cmd"), or alternatively switch to
file(GENERATE) so generator expressions like ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
and ${CMAKE_PROJECT_NAME} are evaluated at generation time; target symbols to
edit: the existing file(WRITE) invocation, add_custom_command,
CMAKE_PROJECT_NAME, CMAKE_RUNTIME_OUTPUT_DIRECTORY, and qtmesh.cmd.
src/CLIPipeline.cpp (1)

555-568: Consider extracting shared Assimp flags constant.

These flags duplicate the ones in src/Assimp/Importer.cpp (lines 41-54). If the base import flags change, this copy could drift out of sync.

💡 Suggestion: Extract shared constant

Consider defining a shared constant (e.g., in Importer.h or a common header):

// In a shared location
constexpr unsigned int kStandardAssimpFlags = 
    aiProcess_CalcTangentSpace | aiProcess_JoinIdenticalVertices | ...;

Then both Importer.cpp::loadModel and CLIPipeline.cpp::cmdFix can reference it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CLIPipeline.cpp` around lines 555 - 568, The aiProcess_* flag set
assigned to stdFlags in CLIPipeline.cpp is duplicated from Assimp/Importer.cpp;
extract a single shared constexpr (e.g., kStandardAssimpFlags) into a common
header (for example Importer.h or a new AssimpFlags.h) and replace the local
stdFlags usage in CLIPipeline.cpp::cmdFix with that constant; likewise update
Importer.cpp::loadModel to use the same kStandardAssimpFlags so both locations
(stdFlags and loadModel) reference the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/CLIPipeline.cpp`:
- Around line 555-568: The aiProcess_* flag set assigned to stdFlags in
CLIPipeline.cpp is duplicated from Assimp/Importer.cpp; extract a single shared
constexpr (e.g., kStandardAssimpFlags) into a common header (for example
Importer.h or a new AssimpFlags.h) and replace the local stdFlags usage in
CLIPipeline.cpp::cmdFix with that constant; likewise update
Importer.cpp::loadModel to use the same kStandardAssimpFlags so both locations
(stdFlags and loadModel) reference the single source of truth.

In `@src/CMakeLists.txt`:
- Around line 279-299: The Windows wrapper is being created with file(WRITE) at
configure-time causing inconsistency with the macOS/Linux add_custom_command(...
POST_BUILD) flow; replace the configure-time file(WRITE) for "qtmesh.cmd" with a
POST_BUILD step (use add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo_append / file generation) that writes the same
"@echo off\n\"%~dp0${CMAKE_PROJECT_NAME}.exe\" --cli %*\n" into
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtmesh.cmd"), or alternatively switch to
file(GENERATE) so generator expressions like ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
and ${CMAKE_PROJECT_NAME} are evaluated at generation time; target symbols to
edit: the existing file(WRITE) invocation, add_custom_command,
CMAKE_PROJECT_NAME, CMAKE_RUNTIME_OUTPUT_DIRECTORY, and qtmesh.cmd.

In `@src/test_main.cpp`:
- Around line 54-62: The comment next to the Ogre::LogManager::createLog call is
misleading about its parameters; update the inline comment near
Ogre::LogManager::getSingletonPtr()/createLog("ogre.log", true, false, true) to
reflect the actual signature (name, defaultLog, debuggerOutput,
suppressFileOutput) and state that the call sets defaultLog=true, disables
debuggerOutput (false) and enables suppressFileOutput (true), leaving
qInstallMessageHandler(testMessageHandler) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c6fc995-fbc8-44a4-b1b2-97ef9c6bbc16

📥 Commits

Reviewing files that changed from the base of the PR and between 7f0cdb2 and 483758e.

📒 Files selected for processing (14)
  • CLAUDE.md
  • README.md
  • src/AnimationMerger.cpp
  • src/AnimationMerger.h
  • src/Assimp/Importer.cpp
  • src/Assimp/Importer.h
  • src/CLIPipeline.cpp
  • src/CLIPipeline.h
  • src/CLIPipeline_test.cpp
  • src/CMakeLists.txt
  • src/MeshImporterExporter.cpp
  • src/MeshImporterExporter.h
  • src/main.cpp
  • src/test_main.cpp

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 483758ef68

ℹ️ 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".

return 1;
}

AnimationMerger::renameAnimation(skel.get(), oldName.toStdString(), newName.toStdString());

Choose a reason for hiding this comment

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

P1 Badge Guard anim rename against existing target names

The anim --rename path checks that oldName exists but never checks whether newName already exists before calling AnimationMerger::renameAnimation. In that case, the rename path attempts to create a duplicate animation name on the skeleton and can raise an Ogre exception/abort instead of returning a controlled CLI error, so a user typo can terminate the command unexpectedly.

Useful? React with 👍 / 👎.


// Load animation files
for (const auto& f : mergeAnimFiles)
MeshImporterExporter::importer({f});

Choose a reason for hiding this comment

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

P2 Badge Fail merge when any input animation file cannot be loaded

cmdMergeAnimations imports each file in mergeAnimFiles but does not verify that each import actually succeeded. If one path is wrong (or unreadable) while others load, the command can still succeed and write output missing expected animations, which is a silent data-loss behavior for batch pipelines.

Useful? React with 👍 / 👎.

Document all qtmesh commands (info, convert, fix, anim, merge-animations)
in the project landing page. Update existing CLI references to use the
qtmesh command instead of ./QtMeshEditor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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 the current code and only fix it if needed.

Inline comments:
In `@docs/index.html`:
- Around line 650-653: The multiline shell examples using backslash
continuations (e.g., the qtmesh merge-animations example and the other
occurrences around the other examples) assume a POSIX shell and will break when
pasted into Windows cmd.exe; update the docs to either collapse each example
into a single-line command or add a short note stating these snippets use POSIX
`\` line continuations and need to be adapted for Windows (e.g., join into one
line or use PowerShell/cmd alternatives), and apply the same change to the other
examples mentioned in the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 300682ee-88d3-41aa-920f-8cf1dd49c0af

📥 Commits

Reviewing files that changed from the base of the PR and between 483758e and e815fb3.

📒 Files selected for processing (1)
  • docs/index.html

Consolidate the standalone `merge-animations` command into the existing
`anim` subcommand as `--merge`, matching the pattern of `--list` and
`--rename`. Remove legacy merge handling from main.cpp.

Address CodeRabbit review feedback:
- P1: Guard anim --rename against duplicate target animation names
- P2: Verify each merge animation file loaded successfully
- Add POSIX shell note to docs for Windows users

Add 30+ process-based CLI tests covering all subcommands, error paths,
and flag combinations for near-complete CLIPipeline coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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: 6

♻️ Duplicate comments (1)
docs/index.html (1)

650-652: ⚠️ Potential issue | 🟡 Minor

Make this merge example Windows-safe too.

This is still a POSIX multiline snippet, but the shell note now lives much later in the page. If someone copies from this section directly, cmd.exe users will still hit a broken command. Either collapse this one to a single line or add a local note beside it.

💡 Low-friction fix
-qtmesh anim character.fbx \
-  --merge walk.fbx run.fbx jump.fbx idle.fbx \
-  -o character_merged.mesh
+qtmesh anim character.fbx --merge walk.fbx run.fbx jump.fbx idle.fbx -o character_merged.mesh
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/index.html` around lines 650 - 652, The POSIX-style multiline example
"qtmesh anim character.fbx --merge walk.fbx run.fbx jump.fbx idle.fbx -o
character_merged.mesh" will break for cmd.exe users; update the snippet where
that command appears to be Windows-safe by either collapsing it to a single line
("qtmesh anim character.fbx --merge ... -o character_merged.mesh") or adding a
short local note showing the Windows cmd.exe equivalent (use ^ line-continuation
or recommend pasting the single-line command) so Windows users can run the
"qtmesh anim" merge example without errors.
🧹 Nitpick comments (1)
src/CLIPipeline_test.cpp (1)

267-285: These tests never exercise the qtmesh entrypoint.

Lines 267-285 always return QtMeshEditor(.exe), so this suite validates subcommand auto-detection but not the new symlink/cmd wrapper path. A broken qtmesh launcher or executable-name detection in src/main.cpp would still ship green.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CLIPipeline_test.cpp` around lines 267 - 285, The test helper
findAppBinary currently always returns the QtMeshEditor binary, so tests never
exercise the new 'qtmesh' entrypoint; update findAppBinary to first look for a
'qtmesh' launcher/symlink (e.g., testBinDir + "/qtmesh" and on Windows
"/qtmesh.exe", and the macOS equivalent inside the .app bundle if applicable)
and return it when present, falling back to QtMeshEditor only if no 'qtmesh' is
found; additionally adjust or add tests to invoke both executable names so
subcommand auto-detection and the new wrapper path are validated (references:
function findAppBinary).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/index.html`:
- Around line 679-739: Add a short platform note clarifying that on Windows the
shipped wrapper is named qtmesh.cmd (so examples using "qtmesh" should be run as
"qtmesh.cmd" on Windows); update the explanatory paragraph near the command
examples (the block that references qtmesh, multiline continuations, and flags
like --verbose/--help) to include a parenthetical or sentence: "On Windows use
qtmesh.cmd" and, if helpful, a brief note to join multiline examples into one
line for PowerShell—leave all existing examples using "qtmesh" unchanged for
POSIX.

In `@README.md`:
- Around line 48-72: Update the README's CLI section to note that on Windows the
executable is shipped as the wrapper "qtmesh.cmd" (instead of "qtmesh"), and
adjust examples or add a short sentence: mention both names (qtmesh /
qtmesh.cmd), suggest using "qtmesh.cmd" on Windows or adding the app folder to
PATH, and recommend using "--help" or "--verbose" the same way; reference the
command examples (qtmesh info, qtmesh convert, qtmesh fix, qtmesh anim) so
readers know to substitute qtmesh.cmd on Windows when copying those commands.

In `@src/CLIPipeline_test.cpp`:
- Around line 400-411: Tests like TEST(CLIPipelineCLI, InfoNonexistentFile) (and
the other tests at the listed ranges) hard-code POSIX /tmp paths; update them to
use QDir::tempPath() or create a QTemporaryDir and build file paths with
QDir::separator() (or QDir::toNativeSeparators) so the tests run on Windows too.
Replace occurrences of "/tmp/nonexistent_file_12345.fbx" (and other "/tmp/..."
strings in the other tests) by constructing the path from QDir::tempPath() +
QDir::separator() + "nonexistent_file_12345.fbx" or by creating a QTemporaryDir
and using its path()/filePath("nonexistent_file_12345.fbx"); apply the same
change to the tests in the other ranges noted in the comment.

In `@src/CLIPipeline.cpp`:
- Around line 428-435: The loop over entities writes one top-level JSON object
per entity (using formatMeshInfoJson) which produces invalid JSON when multiple
entities exist; change the logic in the entities loop so that when jsonOutput is
true you collect each MeshInfo (from extractMeshInfo) and produce a single JSON
array (or top-level object with an array) before calling cliWrite once, rather
than calling cliWrite per entity; keep the existing per-entity text path
(formatMeshInfoText) unchanged and only change the jsonOutput branch to
aggregate results and serialize once.
- Around line 341-360: Remove the unnecessary QApplication creations in the CLI
help/version/no-command paths so the binary won't try to initialize X11 before
deciding CLI-only behavior: when detecting "--help", "-h", "--version", "-v", or
no subcommand, call printUsage() or printVersion() directly (they use
cliWrite()) and return without instantiating QApplication; leave the main
QApplication instantiation used for rendering commands intact (the later
QApplication created for commands that require Ogre should remain), and ensure
no other code in these early-exit branches triggers GUI platform initialization
(this avoids the forceX11PlatformIfNeeded() side-effect causing X11 connection
attempts).

In `@src/main.cpp`:
- Around line 61-69: The CLI auto-detection in main.cpp sets cliMode by checking
argv[1] only, so when a flag like --verbose precedes the subcommand (e.g.,
"--verbose info") the app falls back to GUI; update the detection in main() (the
cliMode logic) to scan argv[] for either the explicit "--cli" or any supported
subcommand token ("info","fix","convert","anim") while ignoring known option
flags such as "--verbose" (i.e., treat "-" or "--" prefixed tokens as options to
skip) so that CLIPipeline::run() is invoked when a supported subcommand appears
anywhere in the argument list even if preceded by options.

---

Duplicate comments:
In `@docs/index.html`:
- Around line 650-652: The POSIX-style multiline example "qtmesh anim
character.fbx --merge walk.fbx run.fbx jump.fbx idle.fbx -o
character_merged.mesh" will break for cmd.exe users; update the snippet where
that command appears to be Windows-safe by either collapsing it to a single line
("qtmesh anim character.fbx --merge ... -o character_merged.mesh") or adding a
short local note showing the Windows cmd.exe equivalent (use ^ line-continuation
or recommend pasting the single-line command) so Windows users can run the
"qtmesh anim" merge example without errors.

---

Nitpick comments:
In `@src/CLIPipeline_test.cpp`:
- Around line 267-285: The test helper findAppBinary currently always returns
the QtMeshEditor binary, so tests never exercise the new 'qtmesh' entrypoint;
update findAppBinary to first look for a 'qtmesh' launcher/symlink (e.g.,
testBinDir + "/qtmesh" and on Windows "/qtmesh.exe", and the macOS equivalent
inside the .app bundle if applicable) and return it when present, falling back
to QtMeshEditor only if no 'qtmesh' is found; additionally adjust or add tests
to invoke both executable names so subcommand auto-detection and the new wrapper
path are validated (references: function findAppBinary).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23cfef57-8e51-482d-8b83-2a79b4b20885

📥 Commits

Reviewing files that changed from the base of the PR and between e815fb3 and 30162c0.

📒 Files selected for processing (9)
  • CLAUDE.md
  • README.md
  • docs/index.html
  • src/CLIPipeline.cpp
  • src/CLIPipeline.h
  • src/CLIPipeline_test.cpp
  • src/MergeAnimationsCLI_test.cpp
  • src/TestHelpers.h
  • src/main.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • CLAUDE.md

- Remove unnecessary QApplication from --help/--version/no-command paths
  (fixes crash on headless X11 systems, speeds up help/version output)
- Fix info --json for multi-entity imports: emit single object for one
  entity, JSON array for multiple entities
- Fix CLI auto-detection to handle --verbose before subcommand
  (e.g., "QtMeshEditor --verbose info foo.fbx" now routes to CLI)
- Replace hard-coded /tmp/ paths in tests with QDir::tempPath() for
  Windows portability
- Add Windows qtmesh.cmd note to docs/index.html and README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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

🧹 Nitpick comments (2)
src/CLIPipeline.cpp (2)

678-686: Minor: Single-dash flags may be collected as merge files.

The loop at lines 681-682 stops collecting merge files when it sees -- prefixed flags or -o, but single-dash flags (if any were added later) would be incorrectly collected as filenames. Consider using arg.startsWith("-") for consistency:

💡 Suggested fix
             // Collect files until next --flag or end
-            while (i + 1 < argc && QString(argv[i + 1]).left(2) != "--"
-                   && QString(argv[i + 1]) != "-o") {
+            while (i + 1 < argc) {
+                QString next(argv[i + 1]);
+                if (next.startsWith("-")) break;
                 mergeFiles.append(QString(argv[++i]));
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CLIPipeline.cpp` around lines 678 - 686, The merge-file collection loop
in the "--merge" branch (variables: arg, mergeMode, mergeFiles) currently stops
only for args beginning with "--" or for "-o", which allows single-dash flags to
be mistaken for filenames; update the while-condition to stop when the next
token starts with "-" (e.g., use QString(argv[i+1]).startsWith("-")) so any flag
(single- or double-dash) halts collection, then continue appending files as
before.

540-542: In-place overwrite may corrupt file if export fails.

When no -o is specified, outputPath defaults to inputPath (line 541). If the export at line 613 fails midway, the original file may be corrupted or truncated. CLI tools typically use atomic writes (write to temp file, then rename on success) to prevent data loss.

This is a minor concern since the user is explicitly choosing to overwrite, but worth considering for robustness.

💡 Optional: Atomic write pattern
+    // For in-place operations, write to temp file first
+    QString actualOutput = outputPath;
+    bool inPlace = (outputPath == inputPath);
+    if (inPlace) {
+        actualOutput = outputPath + ".tmp";
+    }
+
     QFileInfo outFi(outputPath);
-    int result = MeshImporterExporter::exporter(node, outFi.absoluteFilePath(), fmt);
+    QFileInfo actualOutFi(actualOutput);
+    int result = MeshImporterExporter::exporter(node, actualOutFi.absoluteFilePath(), fmt);
     if (result != 0) {
         err() << "Error: Export failed." << Qt::endl;
+        if (inPlace) QFile::remove(actualOutput);
         return 1;
     }
+    if (inPlace) {
+        QFile::remove(outputPath);
+        QFile::rename(actualOutput, outputPath);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CLIPipeline.cpp` around lines 540 - 542, The code currently sets
outputPath = inputPath which risks corrupting the original if the export fails;
change the export to perform an atomic write by creating a temporary file in the
same directory (e.g., outputPath + ".tmp" or unique suffix), write the export
output to that temp file (ensure data is flushed/synced), and only on successful
completion replace/rename the temp file to outputPath (atomic rename/replace),
cleaning up the temp file on error; reference the variables outputPath and
inputPath and the existing export/write routine so you write to a temp path
first and then rename/replace the original.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/CLIPipeline.cpp`:
- Around line 151-160: The conversion of hiddenWidget->winId() to string must
follow OgreWidget.cpp's platform-specific pattern: when building for Windows
cast hiddenWidget->winId() to unsigned long before calling
Ogre::StringConverter::toString, but on non-Windows platforms call
Ogre::StringConverter::toString with the WId directly so the correct overload is
used; update the conversion used in the CLIPipeline.cpp block that prepares
params["externalWindowHandle"] (the hiddenWidget usage before
Manager::getSingleton()->getRoot()->createRenderWindow) to implement this
conditional cast, and apply the same change to the analogous code in
TestHelpers.h to keep behavior consistent across both places.

---

Nitpick comments:
In `@src/CLIPipeline.cpp`:
- Around line 678-686: The merge-file collection loop in the "--merge" branch
(variables: arg, mergeMode, mergeFiles) currently stops only for args beginning
with "--" or for "-o", which allows single-dash flags to be mistaken for
filenames; update the while-condition to stop when the next token starts with
"-" (e.g., use QString(argv[i+1]).startsWith("-")) so any flag (single- or
double-dash) halts collection, then continue appending files as before.
- Around line 540-542: The code currently sets outputPath = inputPath which
risks corrupting the original if the export fails; change the export to perform
an atomic write by creating a temporary file in the same directory (e.g.,
outputPath + ".tmp" or unique suffix), write the export output to that temp file
(ensure data is flushed/synced), and only on successful completion
replace/rename the temp file to outputPath (atomic rename/replace), cleaning up
the temp file on error; reference the variables outputPath and inputPath and the
existing export/write routine so you write to a temp path first and then
rename/replace the original.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee73b0bc-9bab-469d-88d4-50d06ecb1cd5

📥 Commits

Reviewing files that changed from the base of the PR and between 30162c0 and e35ed35.

📒 Files selected for processing (5)
  • README.md
  • docs/index.html
  • src/CLIPipeline.cpp
  • src/CLIPipeline_test.cpp
  • src/main.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/CLIPipeline_test.cpp

Comment on lines +151 to +160
try {
Ogre::NameValuePairList params;
params["externalWindowHandle"] = Ogre::StringConverter::toString(
static_cast<unsigned long>(hiddenWidget->winId()));
#ifdef Q_OS_MACOS
params["macAPI"] = "cocoa";
params["macAPICocoaUseNSView"] = "true";
#endif
Manager::getSingleton()->getRoot()->createRenderWindow(
"CLIHidden", 1, 1, false, &params);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other parts of the codebase handle winId() to externalWindowHandle conversion
rg -n "externalWindowHandle" src/ -A 2 -B 2

Repository: fernandotonon/QtMeshEditor

Length of output: 1273


🏁 Script executed:

#!/bin/bash
# Check how OgreWidget.cpp constructs winHandle
sed -n '140,180p' src/OgreWidget.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1164


🏁 Script executed:

#!/bin/bash
# Look for any other references to winId() and platform-specific handling
rg -n "winId()" src/ -B 2 -A 2

Repository: fernandotonon/QtMeshEditor

Length of output: 1469


🏁 Script executed:

#!/bin/bash
# Check if there are any compilation issues or comments about WId casting
rg -n "WId|unsigned long.*winId" src/ -B 2 -A 2

Repository: fernandotonon/QtMeshEditor

Length of output: 1079


🏁 Script executed:

#!/bin/bash
# Look at the full context of TestHelpers.h initOgreHeadless to compare
sed -n '110,130p' src/TestHelpers.h

Repository: fernandotonon/QtMeshEditor

Length of output: 748


🏁 Script executed:

#!/bin/bash
# Check if StringConverter has overloads or documentation for different types
rg -n "StringConverter::toString" src/ | head -20

Repository: fernandotonon/QtMeshEditor

Length of output: 2847


🏁 Script executed:

#!/bin/bash
# Look for any Ogre-related documentation or comments about WId handling
rg -n "StringConverter" src/ -B 1 -A 1 --context=2

Repository: fernandotonon/QtMeshEditor

Length of output: 50382


Align with OgreWidget.cpp's platform-specific pattern for window handle conversion.

The code lacks platform-specific handling like OgreWidget.cpp uses. OgreWidget.cpp demonstrates the proper approach: cast to unsigned long on Windows, but pass winId() directly to StringConverter::toString() on other platforms, since the function has overloads for the WId type. Apply this same pattern here for consistency:

💡 Suggested fix
         Ogre::NameValuePairList params;
+#ifdef WIN32
+        params["externalWindowHandle"] = Ogre::StringConverter::toString(
+            (unsigned long)(hiddenWidget->winId()));
+#else
         params["externalWindowHandle"] = Ogre::StringConverter::toString(
-            static_cast<unsigned long>(hiddenWidget->winId()));
+            hiddenWidget->winId());
+#endif

Apply the same fix to TestHelpers.h (line 119–120) for consistency across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CLIPipeline.cpp` around lines 151 - 160, The conversion of
hiddenWidget->winId() to string must follow OgreWidget.cpp's platform-specific
pattern: when building for Windows cast hiddenWidget->winId() to unsigned long
before calling Ogre::StringConverter::toString, but on non-Windows platforms
call Ogre::StringConverter::toString with the WId directly so the correct
overload is used; update the conversion used in the CLIPipeline.cpp block that
prepares params["externalWindowHandle"] (the hiddenWidget usage before
Manager::getSingleton()->getRoot()->createRenderWindow) to implement this
conditional cast, and apply the same change to the analogous code in
TestHelpers.h to keep behavior consistent across both places.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 8, 2026

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