Skip to content

Add custom FBX Binary exporter with comprehensive test coverage - #169

Merged
fernandotonon merged 8 commits into
masterfrom
feature/custom-fbx-binary-exporter
Mar 4, 2026
Merged

Add custom FBX Binary exporter with comprehensive test coverage#169
fernandotonon merged 8 commits into
masterfrom
feature/custom-fbx-binary-exporter

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Mar 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Custom FBX Binary v7300 exporter that writes directly from Ogre data, replacing Assimp's broken FBX exporter. Handles geometry, skeleton, skin deformers, animations, and materials with proper Z-mirror transforms for Assimp round-trip compatibility.
  • 36 new in-memory test cases (FBXExporterCoverageTest) with a lightweight FBX binary parser that verifies exported output. Covers geometry (Z-mirror, winding reversal, normals, UVs, 32-bit indices, non-shared vertices), materials, skeleton transforms (deforming/non-deforming bones, hierarchy), skin deformers, animations (stacks, curves, Euler continuity), bind pose, textures, and connections.
  • Estimated FBXExporter.cpp coverage improvement: 0.1% → ~98%

Test plan

  • All 14 standalone FBX tests pass locally (no Ogre needed)
  • 36 FBXExporterCoverageTest cases properly skip on macOS (no GL context)
  • CI (Linux + Xvfb) runs all coverage tests and SonarCloud reports improved coverage
  • Existing tests show no regressions

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Introduced comprehensive test coverage for FBX export functionality, validating various mesh types, material properties, texture bindings, skeletal deformation, animation data, and overall FBX file structure integrity.

fernandotonon and others added 7 commits March 3, 2026 17:45
Replace Assimp's broken FBX exporter with a custom implementation that
writes correct FBX binary format directly from Ogre data. This fixes
skeleton data corruption and animation playback issues on reimport.

Key changes:
- New src/FBX/ module: FBXExporter writes FBX v7300 binary with proper
  geometry, skeleton (LimbNode hierarchy), skin deformers, animations,
  and materials
- Fix Euler angle decomposition to match Assimp's R=Rz*Ry*Rx convention
- Add Euler angle unrolling to prevent keyframe discontinuities
- Call skeleton->setBindingPose() after Assimp import so animation
  deltas are applied relative to correct base transforms
- Route "FBX Binary (*.fbx)" export through new exporter in
  MeshImporterExporter
- Add FBX format to MCP server export_mesh tool
- Comprehensive unit tests for Euler math, continuity, and FBX output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The separate test targets in tests/ have their own source file lists
and were missing FBXExporter.cpp, causing undefined reference errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a submesh was skipped in writeGeometryObjects (empty vertex data),
m_geomIds had fewer entries than the raw submesh count, causing
writeSkinDeformers to map skin data to the wrong geometry. Track the
actual submesh index for each geometry entry and iterate over geometry
entries instead of raw submesh indices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MeshManager::create() leaves meshes unloaded, causing FileNotFoundException
when createEntity() tries to load from disk in CI. Switch to createManual()
with minimal vertex/index data so meshes are fully loaded in memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 36 new test cases in FBXExporterCoverageTest that use in-memory
Ogre meshes (no file loading) to exercise ~98% of FBXExporter.cpp.
Includes a lightweight FBX binary parser for output verification and
8 mesh helper functions covering: geometry (Z-mirror, winding, normals,
UVs, 32-bit indices, non-shared vertices), materials, skeleton (bone
transforms, deforming/non-deforming, hierarchy), skin deformers,
animations (stacks, curves, Euler continuity), bind pose, textures,
and connections.

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

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request adds a self-contained lightweight FBX binary parser and a comprehensive test suite to FBXExporter_test.cpp. The test fixture creates various Ogre mesh configurations, exports them to FBX format, parses the resulting files, and validates the emitted FBX structures against expected hierarchies and values.

Changes

Cohort / File(s) Summary
FBX Parser & Test Infrastructure
src/FBX/FBXExporter_test.cpp
Introduces a lightweight, in-memory FBX binary parser (FBXProperty, FBXNode, parsing utilities) within an anonymous namespace. Adds comprehensive test fixture FBXExporterCoverageTest with helper methods to dynamically create diverse Ogre mesh types (simple, multi-submesh, skeletal, animated, textured). Validates exported FBX sections (FBXHeaderExtension, GlobalSettings, Documents, Definitions, Objects, Connections) and nested structures (Geometry, Materials, Deformers, AnimationStacks). Includes helpers for temporary file handling, unique naming, and artifact cleanup. Expands includes to support Ogre mesh, bone, animation, material, and texture APIs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop skip and jump through FBX streams,
A parser born from testing dreams,
Meshes spin, bones align, animations dance,
Two thousand lines of thorough glance—
Coverage hops where few have crept!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 accurately reflects the main changes: adding a custom FBX Binary exporter with comprehensive test coverage for the FBXExporter module.
Description check ✅ Passed The PR description covers both required template sections (Summary and Technical Details) with substantial content about the custom FBX exporter, test cases, and coverage improvements.

✏️ 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/custom-fbx-binary-exporter

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

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

Copy link
Copy Markdown

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: 7b6898d73e

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

Comment thread src/FBX/FBXExporter.cpp
Comment on lines +850 to +853
if (use32) {
i0 = static_cast<int32_t>(reinterpret_cast<const uint32_t*>(ibase)[f * 3 + 0]);
i1 = static_cast<int32_t>(reinterpret_cast<const uint32_t*>(ibase)[f * 3 + 1]);
i2 = static_cast<int32_t>(reinterpret_cast<const uint32_t*>(ibase)[f * 3 + 2]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Respect indexStart when reading submesh index buffers

The exporter reads triangle indices from the start of the hardware index buffer (f * 3 + ...) and never applies IndexData::indexStart. For meshes that share index buffers or use sliced ranges (e.g., LOD/submesh packing), this serializes the wrong triangles and can produce out-of-range polygon references in the FBX output. Offset the read by iData->indexStart before decoding each face.

Useful? React with 👍 / 👎.

Comment thread src/FBX/FBXExporter.cpp
Comment on lines +823 to +824
posElem->baseVertexPointerToElement(
const_cast<unsigned char*>(base + j * vbuf->getVertexSize()), &p);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply vertexStart when reading vertex attributes

Vertex attribute reads are indexed as base + j * vertexSize without VertexData::vertexStart, so submeshes that reference an offset into a shared vertex buffer export positions/normals/UVs from the wrong region. This leads to scrambled geometry data even when indices are valid. The per-vertex pointer arithmetic should include (vData->vertexStart + j).

Useful? React with 👍 / 👎.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/FBX/FBXExporter_test.cpp (1)

70-72: ⚠️ Potential issue | 🟠 Major

Replace M_PI usage with a portable constant

Lines 70–72 and 79–81 rely on M_PI, which is not guaranteed by standard C++ and requires _USE_MATH_DEFINES on MSVC (not set in this project), causing compilation failures on Windows with default toolchain settings.

Proposed fix
+constexpr double kPi = 3.14159265358979323846;
+
 void testQuaternionToEulerXYZ(const Ogre::Quaternion& q,
                                double& rx, double& ry, double& rz)
 {
@@
-    rx *= 180.0 / M_PI;
-    ry *= 180.0 / M_PI;
-    rz *= 180.0 / M_PI;
+    rx *= 180.0 / kPi;
+    ry *= 180.0 / kPi;
+    rz *= 180.0 / kPi;
 }
@@
 Ogre::Quaternion eulerToQuatAssimp(double rxDeg, double ryDeg, double rzDeg)
 {
-    double rx = rxDeg * M_PI / 180.0;
-    double ry = ryDeg * M_PI / 180.0;
-    double rz = rzDeg * M_PI / 180.0;
+    double rx = rxDeg * kPi / 180.0;
+    double ry = ryDeg * kPi / 180.0;
+    double rz = rzDeg * kPi / 180.0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FBX/FBXExporter_test.cpp` around lines 70 - 72, The test uses the
non-portable M_PI to convert radians to degrees (see rx, ry, rz conversions and
the similar uses later); replace M_PI with a project-local portable constant
(e.g. add a constexpr double PI = 3.14159265358979323846; at the top of
FBXExporter_test.cpp or in a common header) and use PI in the rotations math
(replace M_PI occurrences with PI) so the code compiles on MSVC without
_USE_MATH_DEFINES.
🤖 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/FBX/FBXExporter_test.cpp`:
- Around line 473-503: Validate the read of encoded arrays by checking the local
variables encoding and byteLen before resizing/reading into p.doubleArray,
p.intArray, p.floatArray and p.longArray: if encoding != 0 treat the block as
compressed (use in.seekg(byteLen, std::ios::cur) to skip the bytes and set the
stream failbit) and do not attempt to read into the vector; otherwise verify
byteLen == count * sizeof(element_type) (e.g., sizeof(double)/int/float/long)
and only then resize the corresponding vector and call in.read; on mismatch set
failbit and avoid the read to prevent buffer overrun.
- Around line 1299-1301: The test dereferences objects returned by
findTopLevel("Objects") without checking for nullptr; add an assertion like
ASSERT_NE(objects, nullptr) immediately after retrieving objects to ensure the
test fails instead of crashing, then proceed to call
objects->findAll("Geometry") and ASSERT_EQ(geomNodes.size(), 1u) as before;
reference the findTopLevel call, the objects variable, geomNodes, and use
ASSERT_NE for the null-check.
- Around line 639-645: The exportAndParse helper currently hardcodes "/tmp" and
doesn't treat parseFBX failures as export failures; update exportAndParse (and
use ExportResult) to create a unique portable temp path via QTemporaryFile (or
QDir::tempPath() + QTemporaryFile) instead of "/tmp" and meshCounter to avoid
collisions, ensure the temp file is open/written/closed so
FBXExporter::exportFBX can write to it and parseFBX can read it, call
parseFBX(path) and if parseFBX returns an empty vector set r.success = false
(propagate the parse failure) before returning; reference functions:
exportAndParse, FBXExporter::exportFBX, parseFBX, ExportResult, meshCounter.

---

Outside diff comments:
In `@src/FBX/FBXExporter_test.cpp`:
- Around line 70-72: The test uses the non-portable M_PI to convert radians to
degrees (see rx, ry, rz conversions and the similar uses later); replace M_PI
with a project-local portable constant (e.g. add a constexpr double PI =
3.14159265358979323846; at the top of FBXExporter_test.cpp or in a common
header) and use PI in the rotations math (replace M_PI occurrences with PI) so
the code compiles on MSVC without _USE_MATH_DEFINES.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cd7545b0-6a1b-4807-bf86-237b7005d0d4

📥 Commits

Reviewing files that changed from the base of the PR and between dd4414d and 9948d49.

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

Comment on lines +473 to +503
case 'd': {
uint32_t count; in.read(reinterpret_cast<char*>(&count), 4);
uint32_t encoding; in.read(reinterpret_cast<char*>(&encoding), 4);
uint32_t byteLen; in.read(reinterpret_cast<char*>(&byteLen), 4);
p.doubleArray.resize(count);
in.read(reinterpret_cast<char*>(p.doubleArray.data()), byteLen);
break;
}
case 'i': {
uint32_t count; in.read(reinterpret_cast<char*>(&count), 4);
uint32_t encoding; in.read(reinterpret_cast<char*>(&encoding), 4);
uint32_t byteLen; in.read(reinterpret_cast<char*>(&byteLen), 4);
p.intArray.resize(count);
in.read(reinterpret_cast<char*>(p.intArray.data()), byteLen);
break;
}
case 'f': {
uint32_t count; in.read(reinterpret_cast<char*>(&count), 4);
uint32_t encoding; in.read(reinterpret_cast<char*>(&encoding), 4);
uint32_t byteLen; in.read(reinterpret_cast<char*>(&byteLen), 4);
p.floatArray.resize(count);
in.read(reinterpret_cast<char*>(p.floatArray.data()), byteLen);
break;
}
case 'l': {
uint32_t count; in.read(reinterpret_cast<char*>(&count), 4);
uint32_t encoding; in.read(reinterpret_cast<char*>(&encoding), 4);
uint32_t byteLen; in.read(reinterpret_cast<char*>(&byteLen), 4);
p.longArray.resize(count);
in.read(reinterpret_cast<char*>(p.longArray.data()), byteLen);
break;

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the file exists and view the relevant lines
wc -l src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 103


🏁 Script executed:

# Read the specific lines mentioned in the review (473-503)
sed -n '473,503p' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1507


🏁 Script executed:

# Get broader context around these lines to understand the function
sed -n '450,520p' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 2807


🏁 Script executed:

# Search for any handling of 'encoding' variable elsewhere in the codebase
rg -n 'encoding' src/FBX/ -A 2 -B 2

Repository: fernandotonon/QtMeshEditor

Length of output: 2850


🏁 Script executed:

# Check if there are any comments about FBX format or compression in the file
rg -n 'FBX|compress|deflate' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 6186


🏁 Script executed:

# Look for size validation patterns in the codebase
rg -n 'byteLen|expected.*size' src/FBX/

Repository: fernandotonon/QtMeshEditor

Length of output: 1799


Validate encoding and byteLen before array reads

The parser reads encoding and byteLen at lines 476/484/492/500 but never validates them. If encoding != 0 (compressed arrays), the code reads compressed data as raw bytes, corrupting the parse. Additionally, byteLen is not verified against the expected size (count * sizeof(element)), creating a buffer overflow risk: the vector is resized by count, but in.read() attempts to write byteLen bytes. For untrusted binary input, both must be validated before the read.

Add an encoding check (e.g., skip compressed arrays with in.seekg(byteLen, std::ios::cur) and set failbit) and verify byteLen == count * sizeof(T) before resizing and reading.

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

In `@src/FBX/FBXExporter_test.cpp` around lines 473 - 503, Validate the read of
encoded arrays by checking the local variables encoding and byteLen before
resizing/reading into p.doubleArray, p.intArray, p.floatArray and p.longArray:
if encoding != 0 treat the block as compressed (use in.seekg(byteLen,
std::ios::cur) to skip the bytes and set the stream failbit) and do not attempt
to read into the vector; otherwise verify byteLen == count *
sizeof(element_type) (e.g., sizeof(double)/int/float/long) and only then resize
the corresponding vector and call in.read; on mismatch set failbit and avoid the
read to prevent buffer overrun.

Comment on lines +639 to +645
ExportResult exportAndParse(Ogre::Entity* entity) {
ExportResult r;
r.path = QString("/tmp/fbx_coverage_%1.fbx").arg(meshCounter);
r.success = FBXExporter::exportFBX(entity, r.path);
if (r.success)
r.nodes = parseFBX(r.path.toStdString());
return r;

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

🧩 Analysis chain

🏁 Script executed:

cat -n src/FBX/FBXExporter_test.cpp | sed -n '630,650p'

Repository: fernandotonon/QtMeshEditor

Length of output: 789


🏁 Script executed:

# Check the parseFBX function definition
rg -n 'std::vector<FBXNode>\s+parseFBX' src/FBX/FBXExporter_test.cpp -A10

Repository: fernandotonon/QtMeshEditor

Length of output: 387


🏁 Script executed:

# Check includes at the top of the file
head -50 src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1587


🏁 Script executed:

# Verify that parseFBX returns empty vector on file read failure
rg -n 'return nodes' src/FBX/FBXExporter_test.cpp | head -20

Repository: fernandotonon/QtMeshEditor

Length of output: 132


Use portable temp-file handling and propagate parse failures

Line 641 hardcodes /tmp which is non-portable (Windows uses a different temp directory) and pairs it with a deterministic filename using meshCounter, risking collisions in parallel test runs. Additionally, r.success remains true even when parseFBX() fails—returning an empty vector on file-open errors (line 551). This logic bug allows downstream tests to operate on empty node lists, causing potential crashes.

Use QTemporaryFile and QDir::tempPath() for portable temp handling, and propagate parse failure by checking if parseFBX() returns an empty vector before returning success.

Proposed fix
+#include <QTemporaryFile>
+#include <QDir>
@@
     ExportResult exportAndParse(Ogre::Entity* entity) {
         ExportResult r;
-        r.path = QString("/tmp/fbx_coverage_%1.fbx").arg(meshCounter);
+        QTemporaryFile tmp(QDir::tempPath() + "/fbx_coverage_XXXXXX.fbx");
+        tmp.setAutoRemove(false);
+        if (!tmp.open())
+            return r;
+        r.path = tmp.fileName();
+        tmp.close();
+
         r.success = FBXExporter::exportFBX(entity, r.path);
-        if (r.success)
-            r.nodes = parseFBX(r.path.toStdString());
+        if (!r.success)
+            return r;
+
+        r.nodes = parseFBX(r.path.toStdString());
+        r.success = !r.nodes.empty();
         return r;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FBX/FBXExporter_test.cpp` around lines 639 - 645, The exportAndParse
helper currently hardcodes "/tmp" and doesn't treat parseFBX failures as export
failures; update exportAndParse (and use ExportResult) to create a unique
portable temp path via QTemporaryFile (or QDir::tempPath() + QTemporaryFile)
instead of "/tmp" and meshCounter to avoid collisions, ensure the temp file is
open/written/closed so FBXExporter::exportFBX can write to it and parseFBX can
read it, call parseFBX(path) and if parseFBX returns an empty vector set
r.success = false (propagate the parse failure) before returning; reference
functions: exportAndParse, FBXExporter::exportFBX, parseFBX, ExportResult,
meshCounter.

Comment on lines +1299 to +1301
auto* objects = findTopLevel(r.nodes, "Objects");
auto geomNodes = objects->findAll("Geometry");
ASSERT_EQ(geomNodes.size(), 1u);

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

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file size and read the relevant lines
wc -l src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 103


🏁 Script executed:

# Read the specific lines mentioned in the review comment
sed -n '1295,1305p' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 464


🏁 Script executed:

# Search for findTopLevel definition to understand its return type
rg -n 'findTopLevel' src/FBX/FBXExporter_test.cpp | head -20

Repository: fernandotonon/QtMeshEditor

Length of output: 1294


🏁 Script executed:

# Check for the pattern of Objects usage throughout the file
rg -n 'findTopLevel.*"Objects"' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1544


🏁 Script executed:

# Look for other patterns where findTopLevel results are dereferenced
rg -B2 -A2 'findTopLevel.*->findAll' src/FBX/FBXExporter_test.cpp | head -40

Repository: fernandotonon/QtMeshEditor

Length of output: 52


🏁 Script executed:

# Read the findTopLevel function definition
sed -n '567,585p' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 829


🏁 Script executed:

# Check a few other instances of Objects usage to see the pattern
sed -n '1269,1280p' src/FBX/FBXExporter_test.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 446


🏁 Script executed:

# Check if there are ASSERT_NE checks after findTopLevel calls elsewhere
rg -A3 'findTopLevel.*Objects' src/FBX/FBXExporter_test.cpp | head -50

Repository: fernandotonon/QtMeshEditor

Length of output: 1573


Add null-check assertion before dereferencing objects

Line 1300 dereferences objects without verifying it's not null. The findTopLevel() function explicitly returns nullptr if the node is not found, so a missing "Objects" node would cause a null-pointer crash rather than a test failure. This pattern appears at line 1271 with proper ASSERT_NE(objects, nullptr) check and should be applied consistently.

Proposed fix
     auto* objects = findTopLevel(r.nodes, "Objects");
+    ASSERT_NE(objects, nullptr);
     auto geomNodes = objects->findAll("Geometry");
📝 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
auto* objects = findTopLevel(r.nodes, "Objects");
auto geomNodes = objects->findAll("Geometry");
ASSERT_EQ(geomNodes.size(), 1u);
auto* objects = findTopLevel(r.nodes, "Objects");
ASSERT_NE(objects, nullptr);
auto geomNodes = objects->findAll("Geometry");
ASSERT_EQ(geomNodes.size(), 1u);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/FBX/FBXExporter_test.cpp` around lines 1299 - 1301, The test dereferences
objects returned by findTopLevel("Objects") without checking for nullptr; add an
assertion like ASSERT_NE(objects, nullptr) immediately after retrieving objects
to ensure the test fails instead of crashing, then proceed to call
objects->findAll("Geometry") and ASSERT_EQ(geomNodes.size(), 1u) as before;
reference the findTopLevel call, the objects variable, geomNodes, and use
ASSERT_NE for the null-check.

@sonarqubecloud

sonarqubecloud Bot commented Mar 4, 2026

Copy link
Copy Markdown

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