Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions qml/TexturePropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ GroupBox {
ThemedButton {
text: "Browse..."
onClicked: {
var selectedFileName = MaterialEditorQML.openFileDialog()
if (selectedFileName !== "") {
MaterialEditorQML.setTextureName(selectedFileName)
var selectedPath = MaterialEditorQML.openFileDialog()
if (selectedPath !== "") {
MaterialEditorQML.loadTextureFile(selectedPath)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/AssetBrowserController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const QStringList AssetBrowserController::s_meshExtensions = {
"fbx", "gltf", "glb", "gltf2", "vrm", "obj", "dae", "stl", "mesh", "3ds", "blend", "ply",
"x", "x3d", "lwo", "lws", "ac", "ms3d", "cob", "scn", "bvh", "irrmesh", "irr",
"mdl", "md2", "md3", "md5mesh", "smd", "ogex", "b3d", "q3d", "nff", "off",
"raw", "ter", "hmp", "assbin", "mesh.xml"
"raw", "ter", "hmp", "assbin", "mesh.xml", "tmd"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

mesh.xml will never match with the current classifier.

openFile(), fileTypeForPath(), and refreshFiles() all pass QFileInfo::suffix(), so model.mesh.xml is still seen as xml and this new allowlist entry stays dead. Switch those call sites to completeSuffix() (or normalize multi-part extensions before lookup) so .mesh.xml can actually be opened and filtered as a mesh.

Suggested fix
-    QString type = classifyExtension(fi.suffix().toLower());
+    QString type = classifyExtension(fi.completeSuffix().toLower());

Apply the same change in:

  • AssetBrowserController::openFile()
  • AssetBrowserController::fileTypeForPath()
  • AssetBrowserController::refreshFiles()
🤖 Prompt for 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.

In `@src/AssetBrowserController.cpp` at line 18, The classifier allowlist includes
a multi-part extension "mesh.xml" but code currently uses QFileInfo::suffix() so
names like model.mesh.xml resolve to "xml" and never match; update
AssetBrowserController::openFile, AssetBrowserController::fileTypeForPath, and
AssetBrowserController::refreshFiles to use QFileInfo::completeSuffix() (or
otherwise normalize multi-part extensions to lowercase) when extracting the
extension before looking up the classifier so ".mesh.xml" entries will be
recognized and filtered as mesh.

};

const QStringList AssetBrowserController::s_textureExtensions = {
Expand Down
19 changes: 19 additions & 0 deletions src/AssetBrowserController_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ TEST_F(AssetBrowserControllerTests, FileTypeClassification) {
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.gltf"), "mesh");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.vrm"), "mesh");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.obj"), "mesh");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.tmd"), "mesh");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.TMD"), "mesh");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.png"), "texture");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.jpg"), "texture");
EXPECT_EQ(abc->fileTypeForPath("/foo/bar.tga"), "texture");
Expand All @@ -252,6 +254,23 @@ TEST_F(AssetBrowserControllerTests, OpenFileMeshEmitsImportSignal) {
EXPECT_EQ(paths.at(0), meshPath);
}

TEST_F(AssetBrowserControllerTests, OpenFileTmdEmitsImportSignal) {
QTemporaryDir tmpDir;
ASSERT_TRUE(tmpDir.isValid());

const QString meshPath = tmpDir.path() + "/car.tmd";
QFile(meshPath).open(QIODevice::WriteOnly);

auto* abc = AssetBrowserController::instance();
QSignalSpy spy(abc, &AssetBrowserController::importMeshRequested);

abc->openFile(meshPath);
EXPECT_EQ(spy.count(), 1);
const QStringList paths = spy.at(0).at(0).toStringList();
EXPECT_EQ(paths.size(), 1);
EXPECT_EQ(paths.at(0), meshPath);
}

TEST_F(AssetBrowserControllerTests, OpenFileNonexistentDoesNothing) {
auto* abc = AssetBrowserController::instance();
QSignalSpy spy(abc, &AssetBrowserController::importMeshRequested);
Expand Down
3 changes: 2 additions & 1 deletion src/CLIPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ QString CLIPipeline::formatForExtension(const QString& path)
{".x", "X (*.x)"},
{".mesh.xml", "Ogre XML (*.mesh.xml)"},
{".mesh", "Ogre Mesh (*.mesh)"},
{".assbin", "Assimp Binary (*.assbin)"}
{".assbin", "Assimp Binary (*.assbin)"},
{".tmd", "PlayStation TMD (*.tmd)"}
};

for (const ExtensionFormat& entry : extensionFormats) {
Expand Down
6 changes: 6 additions & 0 deletions src/CLIPipeline_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ TEST(CLIPipelineFormatForExtension, Assbin)
EXPECT_EQ(CLIPipeline::formatForExtension("model.assbin"), "Assimp Binary (*.assbin)");
}

TEST(CLIPipelineFormatForExtension, Tmd)
{
EXPECT_EQ(CLIPipeline::formatForExtension("model.tmd"), "PlayStation TMD (*.tmd)");
EXPECT_EQ(CLIPipeline::formatForExtension("MODEL.TMD"), "PlayStation TMD (*.tmd)");
}

TEST(CLIPipelineFormatForExtension, UnknownDefaultsToMesh)
{
EXPECT_EQ(CLIPipeline::formatForExtension("model.xyz"), "Ogre Mesh (*.mesh)");
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ set(TEST_SOURCES "")
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/OgreXML")
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/Assimp")
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/FBX")
ADD_SUBDIRECTORY("${CMAKE_CURRENT_SOURCE_DIR}/PS1")

#file(GLOB UI_FILES ./ui_files/*.ui)
# if we don't include this CMake will not include ui headers properly:
Expand Down Expand Up @@ -507,7 +508,8 @@ if(BUILD_TESTS)
# UnitTests binary directory so that plugins.cfg (PluginFolder=../) can
# find them. The app bundle has these in Contents/MacOS/ via INSTALL
# commands, but UnitTests is a plain executable — not a bundle.
gtest_discover_tests(UnitTests)
# Discover tests at ctest time (not build time) so CI can provide Xvfb/GL env.
gtest_discover_tests(UnitTests DISCOVERY_MODE PRE_TEST)

# Copy RTSS shader resources to the UnitTests binary directory so that
# RTSSResourcesTest can find them without requiring cmake --install.
Expand Down
2 changes: 1 addition & 1 deletion src/MCPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3397,7 +3397,7 @@ QJsonArray MCPServer::buildToolsList()
"'Ogre XML (*.mesh.xml)', 'Collada (*.dae)', 'X (*.x)', 'OBJ (*.obj)', "
"'OBJ without MTL (*.objnomtl)', 'STL (*.stl)', 'PLY (*.ply)', '3DS (*.3ds)', "
"'glTF 2.0 (*.gltf2)', 'glTF 2.0 Binary (*.glb2)', 'Assimp Binary (*.assbin)', "
"'FBX Binary (*.fbx)'. "
"'FBX Binary (*.fbx)', 'PlayStation TMD (*.tmd)'. "
"Default: 'Ogre Mesh (*.mesh)'"}};
inputSchema["properties"] = properties;
inputSchema["required"] = QJsonArray{"path"};
Expand Down
2 changes: 1 addition & 1 deletion src/Manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Manager* Manager:: m_pSingleton = nullptr;

QString Manager::mValidFileExtention = ".mesh .dae .blend .3ds .ase .obj .ifc .xgl .zgl .ply .dxf .lwo "\
".lws .lxo .stl .x .ac .ms3d .cob .scn .bvh .csm .xml .irrmesh .irr .mdl .md2 .md3 "\
".pk3 .mdc .md5 .txt .smd .vta .m3 .3d .b3d .q3d .q3s .nff .nff .off .raw .ter .mdl .hmp .ndo .fbx .glb .gltf .vrm";
".pk3 .mdc .md5 .txt .smd .vta .m3 .3d .b3d .q3d .q3s .nff .nff .off .raw .ter .mdl .hmp .ndo .fbx .glb .gltf .vrm .tmd";

////////////////////////////////////////
/// Static Member to build & destroy
Expand Down
5 changes: 5 additions & 0 deletions src/Manager_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ TEST_F(ManagerHeadlessTest, IsValidFileExtention)
QString stlFile = "print.stl";
EXPECT_TRUE(mgr->isValidFileExtention(stlFile));

QString tmdFile = "model.tmd";
EXPECT_TRUE(mgr->isValidFileExtention(tmdFile));
EXPECT_TRUE(mgr->isValidFileExtention(QStringLiteral("CAR.TMD")));

// Invalid extensions
QString docFile = "readme.doc";
EXPECT_FALSE(mgr->isValidFileExtention(docFile));
Expand All @@ -284,6 +288,7 @@ TEST_F(ManagerHeadlessTest, IsValidFileExtention)
EXPECT_TRUE(validExts.contains(".mesh"));
EXPECT_TRUE(validExts.contains(".fbx"));
EXPECT_TRUE(validExts.contains(".vrm"));
EXPECT_TRUE(validExts.contains(".tmd"));
}

TEST_F(ManagerHeadlessTest, CreateEmptyScene)
Expand Down
131 changes: 121 additions & 10 deletions src/MaterialEditorQML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "QMLMaterialHighlighter.h"
#include "ModelDownloader.h"
#include "RTShaderHelper.h"
#include "PS1/PS1TIM.h"
#include <OgreRTShaderSystem.h>
#include <QDebug>
#include <QFileDialog>
Expand All @@ -28,6 +29,7 @@
#include <OgreLog.h>
#include <OgreScriptCompiler.h>
#include <OgreScriptTranslator.h>
#include <OgreTextureUnitState.h>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
Expand Down Expand Up @@ -857,7 +859,7 @@
}
}

void MaterialEditorQML::setTextureName(const QString &name)

Check failure on line 862 in src/MaterialEditorQML.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=fernandotonon_QtMeshEditor&issues=AZ357NKxjVBDnzzuKIKk&open=AZ357NKxjVBDnzzuKIKk&pullRequest=394
{
if (m_textureName != name) {
m_textureName = name;
Expand All @@ -866,6 +868,20 @@
if (textureUnit && !name.isEmpty() && name != "*Select a texture*") {
// Only set non-empty, valid texture names to avoid OGRE crashes
textureUnit->setTextureName(name.toStdString());

// If this is a per-import PS1 TMD material, default to unlit single-pass once an image is present.
if (m_ogreMaterial) {
const std::string matName = m_ogreMaterial->getName();
if (matName.rfind("TMD/", 0) == 0) {
if (Ogre::Technique* tech = getCurrentTechnique()) {
while (tech->getNumPasses() > 1)
tech->removePass(1);
}
// PS1 .tim often uses color 0x0000 for transparent texels (see PS1TIM::loadTimToOgreImage).
if (Ogre::Pass* pass = getCurrentPass())
pass->setAlphaRejectSettings(Ogre::CMPF_GREATER_EQUAL, 1);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
updateMaterialText();
}

Expand Down Expand Up @@ -966,7 +982,7 @@
nullptr,
tr("Select a texture"),
QStandardPaths::writableLocation(QStandardPaths::PicturesLocation),
tr("Image File (*.bmp *.jpg *.gif *.raw *.png *.tga *.dds)"));
tr("Texture File (*.bmp *.jpg *.jpeg *.gif *.raw *.png *.tga *.dds *.tim)"));

if (filePath.isEmpty()) return;

Expand All @@ -981,23 +997,37 @@
return;
}

const bool isTim = file.suffix().compare(QStringLiteral("tim"), Qt::CaseInsensitive) == 0;
const QString ogreTexName = isTim ? (file.completeBaseName() + QStringLiteral(".tim")) : file.fileName();

try {
// Try to get existing texture
Ogre::TextureManager::getSingleton().getByName(
file.fileName().toStdString(), file.path().toStdString());
ogreTexName.toStdString(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
} catch (...) {
// Load new texture
// Always register user-picked textures into the default group ("General") so materials can find them.
// Creating ad-hoc groups per directory makes TextureUnitState name resolution fail (yellow/black fallback).
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
file.path().toStdString(), "FileSystem", file.path().toStdString());
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
file.path().toStdString(), "FileSystem", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

Ogre::Image image;
image.load(file.fileName().toStdString(), file.path().toStdString());
if (isTim) {
QString err;
if (!PS1TIM::loadTimToOgreImage(filePath, image, &err)) {
emit errorOccurred(QString("Failed to load TIM: %1").arg(err));
return;
}
} else {
image.load(file.fileName().toStdString(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
}
Ogre::TextureManager::getSingleton().loadImage(
file.fileName().toStdString(), file.path().toStdString(), image);
ogreTexName.toStdString(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, image);
}

setTextureName(file.fileName());
setTextureName(ogreTexName);
}
// LCOV_EXCL_STOP

Expand Down Expand Up @@ -1643,7 +1673,27 @@
// Check origin (absolute path)
QString origin = QString::fromStdString(texPtr->getOrigin());
if (!origin.isEmpty() && QFileInfo::exists(origin)) {
return QUrl::fromLocalFile(QFileInfo(origin).absoluteFilePath()).toString();
// QML Image cannot display .tim; for those, generate a PNG preview.
const QString ext = QFileInfo(origin).suffix().toLower();
if (ext != "tim") {
return QUrl::fromLocalFile(QFileInfo(origin).absoluteFilePath()).toString();
}
}

// If we can't return a directly viewable file (e.g., .tim), generate a PNG preview from the GPU texture.
try {
Ogre::Image img;
texPtr->convertToImage(img, true);
const QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
const QString outDir = QDir(dataPath).filePath("texture_previews");
QDir().mkpath(outDir);
const QString outPath = QDir(outDir).filePath(texName + ".png");
QFile::remove(outPath); // ensure stale previews don't linger
img.save(outPath.toStdString());
if (QFileInfo::exists(outPath)) {
return QUrl::fromLocalFile(QFileInfo(outPath).absoluteFilePath()).toString();
}
} catch (...) {
}

// The resource group name is often the directory the model was loaded from
Expand Down Expand Up @@ -2498,18 +2548,79 @@
QApplication::activeWindow(),
"Select Texture File",
startDir,
"Image files (*.jpg *.jpeg *.png *.dds *.tga *.bmp);;All files (*)",
"Texture files (*.jpg *.jpeg *.png *.dds *.tga *.bmp *.tim);;All files (*)",
nullptr,
QFileDialog::DontUseNativeDialog | QFileDialog::DontUseCustomDirectoryIcons
);

if (!selectedFile.isEmpty()) {
return QFileInfo(selectedFile).fileName();
return selectedFile;
} else {
return QString();
}
}

bool MaterialEditorQML::loadTextureFile(const QString &filePath)
{
if (filePath.isEmpty())
return false;

Ogre::TextureUnitState* textureUnit = getCurrentTextureUnit();
if (!textureUnit) {
emit errorOccurred("No texture unit selected.");
return false;
}

QFileInfo file(filePath);
if (!file.exists()) {
emit errorOccurred("Texture file does not exist.");
return false;
}
if (file.fileName().isEmpty()) {
emit errorOccurred("Selected file has an empty name.");
return false;
}

const bool isTim = file.suffix().compare(QStringLiteral("tim"), Qt::CaseInsensitive) == 0;
const QString ogreTexName = isTim ? (file.completeBaseName() + QStringLiteral(".tim")) : file.fileName();
const std::string texNameStd = ogreTexName.toStdString();
const std::string group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;

try {
if (Ogre::TextureManager::getSingleton().getByName(texNameStd, group)) {
setTextureName(ogreTexName);
return true;
}
} catch (...) {
}

try {
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
file.path().toStdString(), "FileSystem", group);
Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(group);

Ogre::Image image;
if (isTim) {
QString err;
if (!PS1TIM::loadTimToOgreImage(filePath, image, &err)) {
emit errorOccurred(QString("Failed to load TIM: %1").arg(err));
return false;
}
} else {
image.load(file.fileName().toStdString(), group);
}
Ogre::TextureManager::getSingleton().loadImage(texNameStd, group, image);
setTextureName(ogreTexName);
return true;
} catch (const std::exception& e) {
emit errorOccurred(QString("Texture load failed: %1").arg(e.what()));
return false;
} catch (...) {
emit errorOccurred("Texture load failed.");
return false;
}
}

QString MaterialEditorQML::openMaterialImportDialog()
{
QString materialsPath = "./media/materials/scripts";
Expand Down Expand Up @@ -3264,7 +3375,7 @@

engine->load(qmlUrl);

} catch (const std::exception& e) {

Check warning on line 3378 in src/MaterialEditorQML.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Catch a more specific exception instead of a generic one.

See more on https://sonarcloud.io/project/issues?id=fernandotonon_QtMeshEditor&issues=AZ353lIdQGEcG29JkoRv&open=AZ353lIdQGEcG29JkoRv&pullRequest=394
qDebug() << "Exception in Material Editor creation:" << e.what();
emit errorOccurred(QString("Material Editor encountered an error: %1").arg(e.what()));
} catch (...) {
Expand Down
1 change: 1 addition & 0 deletions src/MaterialEditorQML.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ public slots:
Q_INVOKABLE QString getFileSizeString(const QString &path);
Q_INVOKABLE bool pathExists(const QString &path);
Q_INVOKABLE QString openFileDialog();
Q_INVOKABLE bool loadTextureFile(const QString &filePath);
Q_INVOKABLE QString openMaterialImportDialog();
Q_INVOKABLE QString openMaterialExportDialog(const QString &materialName = "");
Q_INVOKABLE QString showNativeFileDialog(QObject *parentWindow);
Expand Down
30 changes: 29 additions & 1 deletion src/MeshImporterExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ THE SOFTWARE.
#include "Assimp/BoneProcessor.h"
#include "Assimp/AnimationProcessor.h"
#include "CLIPipeline.h"
#include "PS1/PS1TMD.h"
#include "EditableMesh.h"
#include "EditModeController.h"
#include <OgreMaterialManager.h>
Expand Down Expand Up @@ -79,7 +80,8 @@ const QMap<QString, QString> MeshImporterExporter::exportFormats = {
{"glTF 2.0 (*.gltf)", ".gltf"},
{"glTF 2.0 Binary (*.glb)", ".glb"},
{"Assimp Binary (*.assbin)", ".assbin"},
{"FBX Binary (*.fbx)", ".fbx"}
{"FBX Binary (*.fbx)", ".fbx"},
{"PlayStation TMD (*.tmd)", ".tmd"}
};

void MeshImporterExporter::configureCamera(const Ogre::Entity *en)
Expand Down Expand Up @@ -1203,6 +1205,27 @@ void MeshImporterExporter::importer(const QStringList &_uriList, unsigned int ad
AnimationMerger::registerSkeletonUpAxis(en->getMesh()->getSkeleton()->getName(), 1);
applyNormalMapsToEntity(en);
}
else if (!file.suffix().compare(QStringLiteral("tmd"), Qt::CaseInsensitive))
{
const std::string meshName = (file.baseName() + QStringLiteral("_tmd")).toStdString();
Ogre::MeshPtr mesh = PS1TMD::importTmd(file.filePath(), meshName);
if (!mesh) {
QMessageBox::warning(
nullptr,
QStringLiteral("PlayStation TMD"),
QStringLiteral("Could not import %1 — invalid file or unsupported primitive types.")
.arg(file.fileName()));
continue;
}
Comment on lines +1212 to +1219

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether CLIPipeline calls MeshImporterExporter::importer (the function that now shows the QMessageBox)
rg -nP -C3 'MeshImporterExporter::importer\b|MeshImporterExporter::sceneImporter\b' --type=cpp
# And what kind of QApplication the CLI entrypoint creates
rg -nP -C3 'QCoreApplication|QGuiApplication|QApplication' src/CLIPipeline.cpp src/main.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 27458


🏁 Script executed:

# Check the exact context around lines 1212-1219 in MeshImporterExporter.cpp
sed -n '1205,1230p' src/MeshImporterExporter.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1322


🏁 Script executed:

# Also check the other failure paths mentioned in the review (lines 1187, 1200, 1286)
echo "=== .mesh failure path around line 1187 ===" && sed -n '1180,1195p' src/MeshImporterExporter.cpp
echo "=== .xml failure path around line 1200 ===" && sed -n '1193,1210p' src/MeshImporterExporter.cpp
echo "=== Assimp failure path around line 1286 ===" && sed -n '1280,1295p' src/MeshImporterExporter.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 3303


🏁 Script executed:

# Check if there are any TMD import tests that might show how the CLI/headless code path works
rg -n 'tmd|TMD' src/MeshImporterExporter_test.cpp | head -20

Repository: fernandotonon/QtMeshEditor

Length of output: 568


🏁 Script executed:

# Verify what happens when QMessageBox is used in a CLI context without user interaction
# Check if there are any guards or conditions around QMessageBox usage elsewhere
rg -n 'QMessageBox' src/MeshImporterExporter.cpp src/CLIPipeline.cpp src/mainwindow.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 2193


🏁 Script executed:

# Check the context around line 1356 - the export QMessageBox
sed -n '1350,1365p' src/MeshImporterExporter.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 729


🏁 Script executed:

# Check if CLIPipeline's QApplication is created before or after MeshImporterExporter::importer calls
grep -n 'new QApplication\|QApplication a\|MeshImporterExporter::importer' src/CLIPipeline.cpp | head -30

Repository: fernandotonon/QtMeshEditor

Length of output: 726


🏁 Script executed:

# Get the full context for the .xml path failure to understand what return does
sed -n '1200,1210p' src/MeshImporterExporter.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 683


🏁 Script executed:

# Check the function signature to see if return in the middle of the loop exits the whole function
sed -n '1162,1175p' src/MeshImporterExporter.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 616


🏁 Script executed:

# Check if there's a test that exercises the .xml failure path to see expected behavior
rg -n 'mesh.xml|importOgreXmlMesh' src/MeshImporterExporter_test.cpp -A 3 -B 3 | head -40

Repository: fernandotonon/QtMeshEditor

Length of output: 1530


🏁 Script executed:

# Check if the exporter function is meant to be GUI-only or can be called from CLI
rg -n 'MeshImporterExporter::exporter\b' src/*.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 4249


Remove QMessageBox — inconsistent with other import branches and problematic for CLI automation.

The other branches in this loop (.mesh line 1187, .xml line 1200) handle import failure silently with continue; / return; and let Ogre logging surface the error. The new .tmd branch breaks this pattern with a modal QMessageBox::warning(nullptr, ...), which creates two problems:

  1. Inconsistent error handling. Per-format failures in this function are currently log-only; the GUI surfaces them via the existing toast/logging pipeline, not modal dialogs. This is the only exception.
  2. CLI blocking. While CLIPipeline does create a QApplication before calling importer(), a modal dialog in the CLI pipeline still blocks execution waiting for user interaction that won't come in automated/headless scenarios.

Recommend logging the failure and letting the existing presentation layer handle it, matching the pattern used by other format branches.

🛡️ Suggested replacement
                 const std::string meshName = (file.baseName() + QStringLiteral("_tmd")).toStdString();
                 Ogre::MeshPtr mesh = PS1TMD::importTmd(file.filePath(), meshName);
                 if (!mesh) {
-                    QMessageBox::warning(
-                        nullptr,
-                        QStringLiteral("PlayStation TMD"),
-                        QStringLiteral("Could not import %1 — invalid file or unsupported primitive types.")
-                            .arg(file.fileName()));
+                    Ogre::LogManager::getSingleton().logMessage(
+                        QStringLiteral("PS1TMD: Could not import %1 — invalid file or unsupported primitive types.")
+                            .arg(file.fileName()).toStdString(),
+                        Ogre::LML_WARNING);
                     continue;
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!mesh) {
QMessageBox::warning(
nullptr,
QStringLiteral("PlayStation TMD"),
QStringLiteral("Could not import %1 — invalid file or unsupported primitive types.")
.arg(file.fileName()));
continue;
}
if (!mesh) {
Ogre::LogManager::getSingleton().logMessage(
QStringLiteral("PS1TMD: Could not import %1 — invalid file or unsupported primitive types.")
.arg(file.fileName()).toStdString(),
Ogre::LML_WARNING);
continue;
}
🤖 Prompt for 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.

In `@src/MeshImporterExporter.cpp` around lines 1212 - 1219, Remove the modal
QMessageBox::warning call in the .tmd import branch and make it mirror the other
branches by logging the failure and continuing; specifically, delete the
QMessageBox::warning(nullptr, QStringLiteral("PlayStation TMD"), ...) that
executes when mesh is null and instead emit the same log used by the .mesh/.xml
branches (or use qWarning()/OGRE_LOG) with a message including file.fileName(),
then continue; ensure no modal UI is created so CLI/headless runs are not
blocked.


SentryReporter::addBreadcrumb(
QStringLiteral("file.import"),
QStringLiteral("Imported PlayStation TMD: %1").arg(file.fileName()));

sn = Manager::getSingleton()->addSceneNode(file.baseName());
en = Manager::getSingleton()->createEntity(sn, mesh);
applyNormalMapsToEntity(en);
}
else
{
AssimpToOgreImporter importer;
Expand Down Expand Up @@ -1435,6 +1458,11 @@ int MeshImporterExporter::exporter(const Ogre::SceneNode *_sn, const QString &_u
// .material and extracted image files next to the FBX.
if (!ok)
return -1;
} else if (_format == QStringLiteral("PlayStation TMD (*.tmd)")) {
if (!PS1TMD::exportEntity(e, _uri))
return -1;
SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
QStringLiteral("Exported PlayStation TMD: %1").arg(_uri));
} else {
// Export using Assimp — build aiScene directly from Ogre mesh data
try {
Expand Down
Loading
Loading