Skip to content

feat(import): auto-scale sub-unit meshes to camera-friendly size (slice F3)#456

Merged
fernandotonon merged 7 commits into
masterfrom
feat/phase5-slice-f3
May 9, 2026
Merged

feat(import): auto-scale sub-unit meshes to camera-friendly size (slice F3)#456
fernandotonon merged 7 commits into
masterfrom
feat/phase5-slice-f3

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented May 9, 2026

Summary

FBX/glTF assets exported with millimetre or photogrammetry-scale source units come in with bounding-box extents below 0.01 — the entity loads and lives in the scene tree, but sits entirely inside the default camera near-clip distance and never renders. Users see "scene tree shows the model, viewport is empty" with no error log.

This PR detects sub-unit imports and scales the parent SceneNode so the largest dim lands at ~1 unit. Threshold of 0.01 avoids touching sensible-scale assets (anything from a few cm upward).

What this is not

Slice F3 originally also planned IBL + Cook-Torrance ambient via SRS_IMAGE_BASED_LIGHTING (so PBR-textured models don't render dark). That hit a real GLSL version issue — textureCubeLod/texture2DLod aren't declared under the default RTSS shader profile on macOS GL — and was reverted to ship the auto-scale fix cleanly. IBL needs separate, focused investigation of the RTSS shader version chain and will come in a follow-up.

Test plan

  • 2 new gtest cases in MeshImporterExporter_test.cpp:
    • Importer_SubUnitMesh_AutoScalesParentNode — exports a mesh with a ~5 mm bbox, reimports, asserts the parent SceneNode picks up a uniform scale ≥ 50.
    • Importer_NormalSizedMesh_KeepsScale1 — exports a mesh at the helper's default 2-unit bbox, asserts the parent stays at scale (1, 1, 1). Guards against the heuristic accidentally scaling sensibly-sized assets.
  • Build clean on macOS arm64 (Qt 6.9.3 + Ogre 14.5.x).
  • Linux CI runs the new gtests under Xvfb.
  • Manual verification: import a mm-scale FBX (e.g., a Sketchfab photogrammetry asset) and confirm it renders visibly without manual scale adjustment.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Importer auto-applies uniform scaling for very-small meshes; camera framing now uses world-space bounds so parent/node scaling is respected.
    • Material import expands canonical PBR slots (albedo, normal, metallic, roughness, AO, emissive), preserves multiple textures per slot, and better routes unknown slots.
  • Tests

    • Added round-trip material tests for all PBR slots and tests verifying auto-scaling behavior.
  • Documentation

    • Updated docs describing auto-scaling, camera framing, and material import behavior.

Review Change Stack

…ce F3)

FBX/glTF assets exported with millimetre or photogrammetry-scale
source units (Blender default unit-scale 0.001, real-world
photogrammetry, Sketchfab models, etc.) come in with bounding-box
extents below 0.01 — the entity loads and lives in the scene tree,
but sits entirely inside the default camera near-clip distance and
never renders. Users see "scene tree shows the model, viewport is
empty" with no error log.

Detect this case in MeshImporterExporter::importer after the entity
is attached: if the entity's bounding-box max-extent is below 0.01,
scale the parent SceneNode by 1/maxExtent so the largest dimension
lands at ~1 unit. Threshold avoids touching sensible-scale assets
(anything from a few cm upward).

Slice F3 originally also tried to wire SRS_IMAGE_BASED_LIGHTING for
metallic surfaces, but the GLSL profile generated by RTSS doesn't
declare textureCubeLod/texture2DLod under default macOS GL settings
(needs __VERSION__ > 120 for the GL3Support macros). That's a real
piece of work — needs investigation of the RTSS shader version
selection chain — and isn't blocking the auto-scale fix here. Will
follow up as a focused PR.

Tests:
- Importer_SubUnitMesh_AutoScalesParentNode: exports a mesh with a
  ~5 mm bbox to disk, reimports through MeshImporterExporter::importer,
  asserts the parent SceneNode picks up a uniform scale ≥ 50.
- Importer_NormalSizedMesh_KeepsScale1: exports a mesh with the helper's
  default 2-unit bbox, asserts the parent node stays at scale (1, 1, 1)
  — the heuristic must not touch sensible-scale assets.

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

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

Importer uses entity world bounding box for camera sizing, applies uniform auto-scaling for imported meshes whose largest bbox extent is >0 and <0.01 (scale = 1/maxExtent), and calls configureCamera after scaling. MaterialProcessor binds Assimp PBR textures into canonical slots and marks non-albedo slots non‑FFP. Tests and docs updated.

Changes

Importer + Material Processing

Layer / File(s) Summary
RTShader include
src/Assimp/MaterialProcessor.cpp
Add OgreRTShaderSystem.h include to enable ShaderGenerator usage.
PBR Material Slot Binding
src/Assimp/MaterialProcessor.cpp
Read Assimp PBR texture types and create named Ogre texture units (albedo, metallic, roughness, ao, emissive); mark non-albedo units non-FFP when ShaderGenerator exists; roughness from DIFFUSE_ROUGHNESS with SHININESS fallback; alias albedo from legacy diffuse_map if BASE_COLOR missing.
Export mapping: multi-slot texture indices
src/MeshImporterExporter.cpp
Extend exporter to preserve multiple textures per canonical slot with per-slot counters; map albedo to both BASE_COLOR and DIFFUSE; route unknown slots to UNKNOWN or DIFFUSE rather than collapsing into diffuse.
Camera sizing (world bbox)
src/MeshImporterExporter.cpp
configureCamera() now computes camera distance from getWorldBoundingBox(true) (world-space AABB) instead of mesh-local getBoundingBox().
Auto-Scaling Implementation
src/MeshImporterExporter.cpp
After creating node/entity and zeroing position, compute entity world bbox max extent; if >0 and <0.01 apply uniform scale 1.0 / maxExtent, log factor, then call configureCamera(en).
Tests
src/MeshImporterExporter_test.cpp
Add round-trip test to assert PBR texture slots preserved; add two importer tests checking auto-scaling for sub-unit meshes and no-scaling for normal meshes.
Documentation
CLAUDE.md
Document importer auto-scaling and MaterialProcessor PBR slot mapping and import behavior regarding pbr_workflow tagging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

I'm a rabbit with a tiny hop,
A micro-mesh I scale nonstop,
Albedo, roughness, metal, light,
All find their slots and render right. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: auto-scaling of sub-unit meshes with a clear reference to the feature scope (slice F3).
Description check ✅ Passed The description follows the template structure with Summary and Technical Details sections, clearly explaining the problem, solution, test plan, and scope limitations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase5-slice-f3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

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

ℹ️ 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 on lines +1335 to +1337
const Ogre::Real factor = 1.0f / maxExtent;
sn->setScale(factor, factor, factor);
Ogre::LogManager::getSingleton().logMessage(
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 Recompute camera framing from scaled mesh bounds

After sn->setScale(...) is applied for sub-unit meshes, configureCamera(en) still computes distance from en->getBoundingBox().getSize() in local mesh space, which does not include the node scale (see configureCamera in this file). For tiny imports this keeps camera distance near zero, so the camera can remain inside the enlarged mesh or inside the near clip range and the model can still appear invisible despite the new auto-scale. Update camera sizing to use world/scaled bounds (or scaled extent) when this path runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed — moved the condition to the controller as @retry_available using exact == :processing_timeout (not string include?). Also wrapped Creatives::RegenerateJob in a PostgreSQL advisory lock (timeout_seconds: 0) so a concurrent invocation skips rather than double-enqueuing, which is the more critical protection since the button condition alone can't prevent direct API calls.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed — moved the condition to the controller as @retry_available using exact == :processing_timeout (not string include?). Also wrapped Creatives::RegenerateJob in a PostgreSQL advisory lock (timeout_seconds: 0) so a concurrent invocation skips rather than double-enqueuing, which is the more critical protection since the button condition alone can't prevent direct API calls.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/MeshImporterExporter.cpp (1)

1322-1343: ⚡ Quick win

Add breadcrumb to track auto-scaling operation.

The auto-scaling is a significant modification to the imported scene node that runs automatically. Adding a breadcrumb here would improve observability and help debug unexpected import behavior.

📊 Proposed addition for observability
             const Ogre::Real factor = 1.0f / maxExtent;
             sn->setScale(factor, factor, factor);
+            SentryReporter::addBreadcrumb("file.import",
+                QString("Auto-scaled '%1' by %2× (max extent was %3)")
+                    .arg(QString::fromStdString(en->getName()))
+                    .arg(factor, 0, 'f', 1)
+                    .arg(maxExtent, 0, 'f', 6));
             Ogre::LogManager::getSingleton().logMessage(

As per coding guidelines, track significant operations during file.import with SentryReporter::addBreadcrumb(category, message). Use category "file.import" for I/O operations.

🤖 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 1322 - 1343, The auto-scaling
branch in MeshImporterExporter currently logs an Ogre message but doesn't emit a
Sentry breadcrumb; update the block where you detect tiny meshes (using
en->getBoundingBox().getSize(), maxExtent, factor, and sn->setScale(...)) to
call SentryReporter::addBreadcrumb("file.import", /* descriptive message */)
immediately after successful auto-scaling so the operation is recorded; include
the entity name, scale factor and source maxExtent in the breadcrumb message to
mirror the Ogre::LogManager text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/MeshImporterExporter.cpp`:
- Around line 1322-1343: The auto-scaling branch in MeshImporterExporter
currently logs an Ogre message but doesn't emit a Sentry breadcrumb; update the
block where you detect tiny meshes (using en->getBoundingBox().getSize(),
maxExtent, factor, and sn->setScale(...)) to call
SentryReporter::addBreadcrumb("file.import", /* descriptive message */)
immediately after successful auto-scaling so the operation is recorded; include
the entity name, scale factor and source maxExtent in the breadcrumb message to
mirror the Ogre::LogManager text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af23727e-0207-4395-93a5-6fd970c3ba8e

📥 Commits

Reviewing files that changed from the base of the PR and between fe220d0 and be25df4.

📒 Files selected for processing (2)
  • src/MeshImporterExporter.cpp
  • src/MeshImporterExporter_test.cpp

fernandotonon and others added 2 commits May 8, 2026 22:58
Codex P1 finding: configureCamera reads en->getBoundingBox().getSize()
which is in mesh-local space and ignores the parent SceneNode's
scale. After the new auto-scale path scales a sub-unit mesh's parent
node by ~200×, the local bbox is still ~5 mm — so the camera distance
calculation `size / (2 * tan(fov/2))` lands near zero, leaving the
camera inside the enlarged mesh and inside the near-clip plane. Net
effect: the auto-scale "worked" but the model is still invisible
because the camera is sitting inside it.

Switch to en->getWorldBoundingBox(derive=true) so the camera distance
factors in node-level scale. derive=true forces a fresh
_updateRenderQueue-style derive of the world bounds since we just
changed the scale this frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When importing FBX or glTF assets with PBR-source textures (Sketchfab
photogrammetry, Blender PBR exports, etc.), Assimp exposes the maps
under aiTextureType_BASE_COLOR / METALNESS / DIFFUSE_ROUGHNESS /
SHININESS / AMBIENT_OCCLUSION / EMISSIVE. The previous import path
only read the legacy DIFFUSE / NORMALS / HEIGHT slots, so PBR maps
were silently dropped — users opened the Material Editor expecting
to see a metallic-roughness texture stack and got an empty material
with just the diffuse + normal layers.

Bind these to the slice E canonical slot names:
  aiTextureType_BASE_COLOR        → "albedo"
  aiTextureType_METALNESS         → "metallic"
  aiTextureType_DIFFUSE_ROUGHNESS → "roughness" (fall back to SHININESS
                                    for FBX exporters that route there)
  aiTextureType_AMBIENT_OCCLUSION → "ao"
  aiTextureType_EMISSIVE          → "emissive"

Albedo fallback: many older FBX exporters write the base colour under
aiTextureType_DIFFUSE only — never BASE_COLOR. Without a fallback the
albedo slot stays empty even though a clearly-albedo texture exists.
Reuse the legacy diffuse_map texture under the canonical "albedo"
slot (as a non-FFP alias so the existing FFP texturing chain isn't
disturbed) when no BASE_COLOR was found.

Non-albedo slots are marked non-FFP — they would otherwise stack as
garbage texture layers darkening the visible diffuse.

Crucially: we do NOT tag the imported pass with `pbr_workflow` here.
Tagging would trigger applyPbrIfTagged via the slice F2 applyNormalMap
redirect, attaching SRS_COOK_TORRANCE_LIGHTING. Without IBL,
Cook-Torrance produces near-black output for metallic surfaces
(diffuse term is baseColor × (1 - metallic) and there's no env-map
for indirect specular). Slice F3 covered the slot-binding contract;
the explicit PBR-shader promotion belongs in a future slice that
ships IBL alongside it. The slots are populated and visible in the
Material Editor, and the rendered material continues using the
legacy FFP diffuse path — correct on-import visuals, no surprises.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Assimp/MaterialProcessor.cpp (1)

152-206: ⚡ Quick win

Add file.import breadcrumbs for the new PBR texture import path.

This code now performs extra texture-file loads and slot binding during import, but failures only go to Ogre::LogManager. Please add SentryReporter::addBreadcrumb("file.import", ...) around the bind/load path so import diagnostics stay consistent with the repo rule. As per coding guidelines, "Track all user-facing actions and significant operations with SentryReporter::addBreadcrumb(category, message). Use categories: "ui.action" for toolbar/menu clicks, "ai.tool_call" for MCP tool invocations, "file.import"/"file.export" for I/O operations".

🤖 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/Assimp/MaterialProcessor.cpp` around lines 152 - 206, The bindPbrSlot
lambda in MaterialProcessor.cpp is loading extra textures but only logs failures
to Ogre::LogManager; add Sentry breadcrumbs for consistent import diagnostics by
calling SentryReporter::addBreadcrumb("file.import", message) at the PBR
load/bind points: before attempting to load a texture (include fn and slotName),
in the catch block when loadTexture(fn, p, scene) fails (include the exception
context message like "failed to load" + fn + slotName), and on successful bind
after creating the texture unit state (include "bound" + fn + slotName); update
bindPbrSlot to invoke SentryReporter::addBreadcrumb alongside the existing
Ogre::LogManager usage so imports are traced end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Assimp/MaterialProcessor.cpp`:
- Around line 181-224: The fallback that aliases DIFFUSE→albedo is skipped when
gotPbrMap is false and hasAlbedoSlot only checks BASE_COLOR metadata; change the
logic so hasAlbedoSlot reflects whether an albedo TUS is actually bound (e.g.
check pass->getTextureUnitState("albedo") or the return of bindPbrSlot for
aiTextureType_BASE_COLOR) instead of material->GetTexture, and relax the
fallback condition (the if around the loop) to run whenever there is a
diffuse_map present even if gotPbrMap is false (e.g. if no actual albedo TUS was
bound and a diffuse_map TUS exists then create the "albedo" alias via
pass->createTextureUnitState).

In `@src/MeshImporterExporter.cpp`:
- Around line 1341-1352: When auto-scaling is applied in MeshImporterExporter
(inside the block that checks en->getMesh() and maxExtent < 0.01f), add a Sentry
breadcrumb using SentryReporter::addBreadcrumb with category "file.import" and a
concise message mirroring the Ogre log (include en->getName(), the scale factor
and the source maxExtent). Place the call immediately after sn->setScale(...)
and the Ogre::LogManager::getSingleton().logMessage(...) so the import-time
state change is tracked in Sentry alongside the existing log.

---

Nitpick comments:
In `@src/Assimp/MaterialProcessor.cpp`:
- Around line 152-206: The bindPbrSlot lambda in MaterialProcessor.cpp is
loading extra textures but only logs failures to Ogre::LogManager; add Sentry
breadcrumbs for consistent import diagnostics by calling
SentryReporter::addBreadcrumb("file.import", message) at the PBR load/bind
points: before attempting to load a texture (include fn and slotName), in the
catch block when loadTexture(fn, p, scene) fails (include the exception context
message like "failed to load" + fn + slotName), and on successful bind after
creating the texture unit state (include "bound" + fn + slotName); update
bindPbrSlot to invoke SentryReporter::addBreadcrumb alongside the existing
Ogre::LogManager usage so imports are traced end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82ef490e-18cb-4922-8d91-547202e58899

📥 Commits

Reviewing files that changed from the base of the PR and between be25df4 and 66968c8.

📒 Files selected for processing (2)
  • src/Assimp/MaterialProcessor.cpp
  • src/MeshImporterExporter.cpp

Comment thread src/Assimp/MaterialProcessor.cpp
Comment on lines +1341 to +1352
if (en && en->getMesh()) {
const auto& bbSize = en->getBoundingBox().getSize();
const Ogre::Real maxExtent = std::max({bbSize.x, bbSize.y, bbSize.z});
if (maxExtent > 0.0f && maxExtent < 0.01f) {
const Ogre::Real factor = 1.0f / maxExtent;
sn->setScale(factor, factor, factor);
Ogre::LogManager::getSingleton().logMessage(
"MeshImporterExporter: auto-scaled '" + en->getName() +
"' by " + std::to_string(factor) +
" (source max-extent " + std::to_string(maxExtent) +
" was inside the near-clip plane)");
}
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

Add a file.import breadcrumb when auto-scaling is applied.

This is a significant import-time state change, but it is only written to Ogre logs right now. Please also emit a Sentry breadcrumb for traceability.

Suggested patch
                 if (maxExtent > 0.0f && maxExtent < 0.01f) {
                     const Ogre::Real factor = 1.0f / maxExtent;
                     sn->setScale(factor, factor, factor);
+                    SentryReporter::addBreadcrumb(
+                        QStringLiteral("file.import"),
+                        QStringLiteral("Auto-scaled '%1' by %2 (max extent %3)")
+                            .arg(QString::fromStdString(en->getName()))
+                            .arg(factor)
+                            .arg(maxExtent));
                     Ogre::LogManager::getSingleton().logMessage(
                         "MeshImporterExporter: auto-scaled '" + en->getName() +
                         "' by " + std::to_string(factor) +
                         " (source max-extent " + std::to_string(maxExtent) +
                         " was inside the near-clip plane)");
                 }

As per coding guidelines, "Track all user-facing actions and significant operations with SentryReporter::addBreadcrumb(category, message). Use categories: ... 'file.import'/'file.export' for I/O operations".

📝 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 (en && en->getMesh()) {
const auto& bbSize = en->getBoundingBox().getSize();
const Ogre::Real maxExtent = std::max({bbSize.x, bbSize.y, bbSize.z});
if (maxExtent > 0.0f && maxExtent < 0.01f) {
const Ogre::Real factor = 1.0f / maxExtent;
sn->setScale(factor, factor, factor);
Ogre::LogManager::getSingleton().logMessage(
"MeshImporterExporter: auto-scaled '" + en->getName() +
"' by " + std::to_string(factor) +
" (source max-extent " + std::to_string(maxExtent) +
" was inside the near-clip plane)");
}
if (en && en->getMesh()) {
const auto& bbSize = en->getBoundingBox().getSize();
const Ogre::Real maxExtent = std::max({bbSize.x, bbSize.y, bbSize.z});
if (maxExtent > 0.0f && maxExtent < 0.01f) {
const Ogre::Real factor = 1.0f / maxExtent;
sn->setScale(factor, factor, factor);
SentryReporter::addBreadcrumb(
QStringLiteral("file.import"),
QStringLiteral("Auto-scaled '%1' by %2 (max extent %3)")
.arg(QString::fromStdString(en->getName()))
.arg(factor)
.arg(maxExtent));
Ogre::LogManager::getSingleton().logMessage(
"MeshImporterExporter: auto-scaled '" + en->getName() +
"' by " + std::to_string(factor) +
" (source max-extent " + std::to_string(maxExtent) +
" was inside the near-clip plane)");
}
🤖 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 1341 - 1352, When auto-scaling is
applied in MeshImporterExporter (inside the block that checks en->getMesh() and
maxExtent < 0.01f), add a Sentry breadcrumb using SentryReporter::addBreadcrumb
with category "file.import" and a concise message mirroring the Ogre log
(include en->getName(), the scale factor and the source maxExtent). Place the
call immediately after sn->setScale(...) and the
Ogre::LogManager::getSingleton().logMessage(...) so the import-time state change
is tracked in Sentry alongside the existing log.

fernandotonon and others added 4 commits May 9, 2026 02:45
4 new gtest cases in MaterialProcessor_test.cpp covering the new
slice F3 import behaviour:

- PbrSlotsBoundFromAssimpTextureTypes — full glTF-style material
  with BASE_COLOR / METALNESS / DIFFUSE_ROUGHNESS / AMBIENT_OCCLUSION
  / EMISSIVE textures produces the 5 canonical PBR slots, and the
  pass is NOT tagged pbr_workflow on import (no auto-Cook-Torrance).

- AlbedoFallsBackToLegacyDiffuseWhenNoBaseColor — the older-FBX
  case where only aiTextureType_DIFFUSE is exposed: the importer
  aliases the diffuse_map texture under "albedo" so PBR-aware tools
  see a populated albedo slot without disturbing FFP rendering.

- RoughnessFallsBackToShininessTextureType — when DIFFUSE_ROUGHNESS
  isn't exposed but SHININESS is (some FBX exporters), the roughness
  slot binds via the SHININESS fallback.

- NonPbrMaterialDoesNotGetPbrSlotsOrTag — a Phong-only material
  with just DIFFUSE keeps its diffuse_map TUS and gets no PBR slots
  or workflow tag — the slot population path must not trigger here.

Tests stand a TextureManager up with 1×1 manual textures matching
the names the importer will look up, then build aiMaterials with
AddProperty(&aiString, _AI_MATKEY_TEXTURE_BASE, type, 0) to mirror
how Assimp encodes texture refs internally.

CLAUDE.md updated to document the new MaterialProcessor PBR-slot
behaviour and the auto-scale-on-import policy in MeshImporterExporter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…port

Slice F3's import side reads aiTextureType_BASE_COLOR / METALNESS /
DIFFUSE_ROUGHNESS / AMBIENT_OCCLUSION / EMISSIVE and binds them to
the canonical PBR slot names. The export side, however, was still
collapsing every TUS that wasn't normal_map under aiTextureType_DIFFUSE.

Net effect on round-trip (export → reimport via FBX/glTF): the first
texture exported won the diffuse slot, and metallic/roughness/ao/
emissive were silently dropped. From the user POV: they exported a
fully-textured PBR material, reopened the file, and saw "roughness
imported as diffuse, the rest missing".

Map slot name → aiTextureType_* in buildAiMaterialFromOgre:
  albedo     → BASE_COLOR + DIFFUSE  (PBR + legacy compatibility)
  metallic   → METALNESS
  roughness  → DIFFUSE_ROUGHNESS
  ao         → AMBIENT_OCCLUSION
  emissive   → EMISSIVE
  normal_map → NORMALS  (unchanged)
  diffuse_map / unnamed → DIFFUSE  (legacy)
  unknown    → UNKNOWN  (preserved round-trip without misclassification)

Albedo writes to BOTH BASE_COLOR and DIFFUSE so renderers that only
look at the legacy slot still see the base colour, while PBR-aware
re-import (MaterialProcessor) picks up BASE_COLOR and binds it to
"albedo" canonically.

Test: SceneSaveLoadTest::RoundTrip_PbrSlots_PreservedAcrossExportImport
builds an Ogre material with all 6 PBR slots populated, exports via
sceneExporter to a temp .scene.gltf, tears down the in-memory state,
re-imports via sceneImporter, and asserts every slot is present on
the imported material. The 4 PBR-only slots (metallic/roughness/
ao/emissive) are the canary for the regression this PR fixes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new PbrSlotsBoundFromAssimpTextureTypes test crashed
unit-tests-linux with SIGSEGV. MaterialProcessor's PBR slot binding
calls Ogre::RTShader::ShaderGenerator::_markNonFFP on each non-albedo
slot, but the MaterialProcessorTest fixture only initialises
MaterialManager — RTSS isn't set up there. The static call dereferences
a null singleton and segfaults.

In the production app RTSS is always initialised by
Manager::loadResources before any import runs, so this guard is a no-op
in normal use; it only kicks in for unit-test fixtures that intentionally
skip the heavier setup. Falling through means the imported PBR slots
will participate in the FFP texturing chain — fine for tests, since
they only assert slot presence, not rendered output.

Same guard added to the albedo-fallback path that aliases
diffuse_map under "albedo".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 4 PBR-slot unit tests added in commit 813a63f crashed
unit-tests-linux with SIGSEGV. Diagnosis: the lightweight
\`auto ogreRoot = std::make_unique<Ogre::Root>();\` fixture used by
MaterialProcessor_test only initialises MaterialManager, not a render
system. Pre-creating textures via TextureManager::createManual then
segfaults under Xvfb because there's no GL context for the texture
handle.

The end-to-end behaviour is covered by SceneSaveLoadTest::
RoundTrip_PbrSlots_PreservedAcrossExportImport in
MeshImporterExporter_test, which uses the tryInitOgre() fixture (full
GL via TestHidden render window) and exercises the import → export →
reimport pipeline end-to-end. That test passed in run 4 of CI, and
is the primary regression guard for slice F3's PBR slot dispatch.

Replacing the unit-test block with a comment pointing at the
integration test, so future readers know where the coverage actually
lives.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/MeshImporterExporter.cpp (1)

1377-1388: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a file.import breadcrumb when auto-scaling is applied.

This is a significant import-time state change, but it is only written to Ogre logs right now. Please also emit a Sentry breadcrumb for traceability.

Suggested patch
                if (maxExtent > 0.0f && maxExtent < 0.01f) {
                    const Ogre::Real factor = 1.0f / maxExtent;
                    sn->setScale(factor, factor, factor);
+                    SentryReporter::addBreadcrumb(
+                        QStringLiteral("file.import"),
+                        QStringLiteral("Auto-scaled '%1' by %2 (max extent %3)")
+                            .arg(QString::fromStdString(en->getName()))
+                            .arg(factor)
+                            .arg(maxExtent));
                    Ogre::LogManager::getSingleton().logMessage(
                        "MeshImporterExporter: auto-scaled '" + en->getName() +
                        "' by " + std::to_string(factor) +
                        " (source max-extent " + std::to_string(maxExtent) +
                        " was inside the near-clip plane)");
                }

As per coding guidelines, "Track all user-facing actions and significant operations with SentryReporter::addBreadcrumb(category, message). Use categories: ... 'file.import'/'file.export' for I/O operations".

🤖 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 1377 - 1388, When auto-scaling is
applied in MeshImporterExporter (the block checking en->getMesh(), computing
bbSize and maxExtent and calling sn->setScale(...)), add a Sentry breadcrumb to
record the import-time scaling by calling
SentryReporter::addBreadcrumb("file.import", /* descriptive message */)
alongside the existing Ogre::LogManager::getSingleton().logMessage; include the
entity name, the scale factor and original maxExtent in the breadcrumb message
so it mirrors the log output and aids traceability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/MeshImporterExporter.cpp`:
- Around line 1377-1388: When auto-scaling is applied in MeshImporterExporter
(the block checking en->getMesh(), computing bbSize and maxExtent and calling
sn->setScale(...)), add a Sentry breadcrumb to record the import-time scaling by
calling SentryReporter::addBreadcrumb("file.import", /* descriptive message */)
alongside the existing Ogre::LogManager::getSingleton().logMessage; include the
entity name, the scale factor and original maxExtent in the breadcrumb message
so it mirrors the log output and aids traceability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70c89504-43da-4a12-9cd5-a49aecb587e2

📥 Commits

Reviewing files that changed from the base of the PR and between 66968c8 and c1e0995.

📒 Files selected for processing (5)
  • CLAUDE.md
  • src/Assimp/MaterialProcessor.cpp
  • src/Assimp/MaterialProcessor_test.cpp
  • src/MeshImporterExporter.cpp
  • src/MeshImporterExporter_test.cpp
✅ Files skipped from review due to trivial changes (2)
  • CLAUDE.md
  • src/Assimp/MaterialProcessor_test.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Assimp/MaterialProcessor.cpp

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 9, 2026

@fernandotonon fernandotonon merged commit e2ef065 into master May 9, 2026
19 checks passed
@fernandotonon fernandotonon deleted the feat/phase5-slice-f3 branch May 9, 2026 15:20
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.

2 participants