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
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ Three singletons manage core state. All run on the main thread. Access via `Clas

- **MaterialEditorQML** (`src/MaterialEditorQML.h/cpp`): QML_SINGLETON exposing full Ogre material property access (colors, lighting, depth, blending, fog, textures) with undo/redo. QML UI in `qml/`.

### Debug Overlays

- **NormalVisualizer** (`src/NormalVisualizer.h/cpp`): Draws vertex normals as colored lines (|X|=Red, |Y|=Green, |Z|=Blue). Toggled globally via Options → Show Normals menu or MCP `toggle_normals` tool. Supports real-time animation: requests software-skinned normals via `addSoftwareAnimationRequest(true)` and updates each frame for skeletal entities. Overlays attach to dedicated child scene nodes to avoid unsafe `static_cast<Entity*>` crashes in `ObjectItemModel` and `Manager::getEntities()`.
- **BoneWeightOverlay** (`src/BoneWeightOverlay.h/cpp`): Per-entity bone weight heat-map overlay.

### MCP Server

- **MCPServer** (`src/MCPServer.h/cpp`): JSON-RPC 2.0 over stdio + HTTP REST API on configurable port.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cmake_minimum_required(VERSION 3.24.0)
cmake_policy(SET CMP0005 NEW)
cmake_policy(SET CMP0048 NEW) # manages project version

project(QtMeshEditor VERSION 2.9.0 LANGUAGES CXX)
project(QtMeshEditor VERSION 2.10.0 LANGUAGES CXX)
message(STATUS "Building QtMeshEditor version ${PROJECT_VERSION}")

set(QTMESHEDITOR_VERSION_STRING "\"${PROJECT_VERSION}\"")
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ MCPServer.cpp
MCPSettingsDialog.cpp
SentryReporter.cpp
BoneWeightOverlay.cpp
NormalVisualizer.cpp
AnimationMerger.cpp
)

Expand Down Expand Up @@ -88,6 +89,7 @@ MCPServer.h
MCPSettingsDialog.h
SentryReporter.h
BoneWeightOverlay.h
NormalVisualizer.h
AnimationMerger.h
)

Expand Down
36 changes: 36 additions & 0 deletions src/MCPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "MeshImporterExporter.h"
#include "OgreWidget.h"
#include "AnimationWidget.h"
#include "NormalVisualizer.h"
#include <QDebug>
#include <QFile>
#include <QDir>
Expand Down Expand Up @@ -440,6 +441,8 @@
toolResult = toolToggleSkeletonDebug(args);
} else if (name == "toggle_bone_weights") {
toolResult = toolToggleBoneWeights(args);
} else if (name == "toggle_normals") {
toolResult = toolToggleNormals(args);
} else if (name == "merge_animations") {
toolResult = toolMergeAnimations(args);
} else {
Expand Down Expand Up @@ -1883,6 +1886,21 @@
}
}

QJsonObject MCPServer::toolToggleNormals(const QJsonObject &args)

Check warning on line 1889 in src/MCPServer.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function should be declared "const".

See more on https://sonarcloud.io/project/issues?id=fernandotonon_QtMeshEditor&issues=AZy99DRoRsg5kKjonDj7&open=AZy99DRoRsg5kKjonDj7&pullRequest=173
{
if (!m_mainWindow)
return makeErrorResult("Error: MainWindow not available. Run with --with-mcp flag.");

NormalVisualizer* visualizer = m_mainWindow->findChild<NormalVisualizer*>();
if (!visualizer)
return makeErrorResult("Error: NormalVisualizer not found");

bool show = args.contains("show") ? args["show"].toBool() : !visualizer->isVisible();
visualizer->setVisible(show);

return makeSuccessResult(QString("Normals %1").arg(show ? "shown" : "hidden"));
}

QJsonObject MCPServer::toolMergeAnimations(const QJsonObject &args)
{
try {
Expand Down Expand Up @@ -2411,6 +2429,24 @@
));
}

// toggle_normals
{
QJsonObject inputSchema;
inputSchema["type"] = "object";
QJsonObject props;
props["show"] = QJsonObject{{"type", "boolean"}, {"description", "True to show, false to hide. If omitted, toggles the current state."}};
inputSchema["properties"] = props;

tools.append(buildToolDefinition(
"toggle_normals",
"Show or hide vertex normal visualization on all entities in the scene. "
"Normals are displayed as colored lines extending from each vertex, "
"color-coded by direction (|X|=Red, |Y|=Green, |Z|=Blue). "
"Normals follow skeletal animations in real-time.",
inputSchema
));
}

// merge_animations
{
QJsonObject inputSchema;
Expand Down
3 changes: 2 additions & 1 deletion src/MCPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ private slots:
QJsonObject toolPlayAnimation(const QJsonObject &args);
QJsonObject toolToggleSkeletonDebug(const QJsonObject &args);
QJsonObject toolToggleBoneWeights(const QJsonObject &args);
QJsonObject toolToggleNormals(const QJsonObject &args);
QJsonObject toolMergeAnimations(const QJsonObject &args);

// Animation
Expand Down Expand Up @@ -192,7 +193,7 @@ private slots:
// MCP Protocol version
static constexpr const char* MCP_VERSION = "2024-11-05";
static constexpr const char* SERVER_NAME = "QtMeshEditor";
static constexpr const char* SERVER_VERSION = "1.0.0";
static constexpr const char* SERVER_VERSION = "1.1.0";
};

#endif // MCPSERVER_H
23 changes: 22 additions & 1 deletion src/MCPServer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ TEST_F(MCPServerTest, HandleToolsList)
"list_skeletal_animations", "get_animation_info", "set_animation_length",
"set_animation_time", "add_keyframe", "remove_keyframe",
"play_animation", "toggle_skeleton_debug", "toggle_bone_weights",
"merge_animations"
"toggle_normals", "merge_animations"
};

for (const QString &tool : knownTools) {
Expand Down Expand Up @@ -2489,3 +2489,24 @@ TEST_F(MCPServerTest, PlayAnimationWithSkeletonEntity)
QJsonObject result = server->callTool("play_animation", args);
EXPECT_FALSE(isError(result));
}

// ==========================================================================
// NEW TESTS: toggle_normals
// ==========================================================================

TEST_F(MCPServerTest, ToggleNormalsNoMainWindow)
{
// Server has no MainWindow set — should fail gracefully
QJsonObject args;
args["show"] = true;
QJsonObject result = server->callTool("toggle_normals", args);
EXPECT_TRUE(isError(result));
EXPECT_TRUE(getResultText(result).contains("MainWindow") ||
getResultText(result).contains("NormalVisualizer"));
}

TEST_F(MCPServerTest, ToggleNormalsIsRecognizedTool)
{
QJsonObject result = server->callTool("toggle_normals", QJsonObject());
EXPECT_FALSE(getResultText(result).contains("Unknown tool"));
}
Loading
Loading