Skip to content

feat(texture): multi-view AI texture bake (front+back projection onto UV0)#729

Merged
fernandotonon merged 9 commits into
masterfrom
feat/multiview-texture-bake
Jun 16, 2026
Merged

feat(texture): multi-view AI texture bake (front+back projection onto UV0)#729
fernandotonon merged 9 commits into
masterfrom
feat/multiview-texture-bake

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Generates a diffuse texture by ControlNet-generating images from multiple camera views (front + back to start) and projection-baking them onto the mesh's existing UV0 atlas — replacing the prior planar bind that was unreliable on RTSS/PBR materials (#403 known limitation). See src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md.

Key idea: the UV layout stays fixed; each view's generated image is re-projected through the exact camera that produced its depth map and rasterized into UV0 (this is a bake, not "recompute UVs from the image").

Slice 1 — reprojection foundation

  • MeshDepthRenderer::renderDepthMapView(entity, size, View) returns the depth image plus the exact view/projection matrices + camera dir used. Built-in front/back/left/right/top/bottom views. renderDepthMap() stays as the front-view delegate so AI: Mesh-aware texture generation (depth-conditioned sd.cpp) #403 is unchanged.
  • MultiViewTextureBaker (pure-data): per triangle × view, facing-weight cull (max(0,-n·viewDir)^p), project to viewport UV, rasterize in UV0 space, sample the view image at the barycentric-interpolated screen UV, accumulate color×weight; resolve by weight + seam-dilate (reuses VertexColorBaker::dilate + TexturePaintBuffer).
  • Unit tests: synthetic ±Z quad + solid images assert front paints, back-only is culled, front+back blends by facing, dilation fills margins, empty-input guards. (Math also validated locally via a standalone harness.)

Slice 2 — front+back orchestration + UV0 bake

  • MultiViewTextureBaker::fromEntity() extracts world-space triangles + UV0 from a live entity (world matrix on positions, inverse-transpose on normals).
  • MaterialEditorQML::generateMeshTextureMultiView(prompt,w,h,strength,views): a state machine that renders each view's depth+matrices, generates one image per view sequentially through the SD worker, and on the final completion bakes the accumulated images onto UV0 and applies the atlas to the entity's diffuse. onSDGenerationError aborts cleanly. Defaults to {front, back}. ENABLE_STABLE_DIFFUSION-guarded.
  • QML: "Bake front + back (projects onto UV map)" sub-toggle under the existing "use selected mesh" checkbox.

Incidental fixes found while building this

  • Keychain prompt regression: migrateLegacySettingsIfNeeded() re-probed the OS secret store on every signed-out launch (SecItemCopyMatching → macOS prompt). Now gated behind a persisted Cloud/legacyMigrationDone flag → probes at most once, ever. Regression test added.
  • Scene tree: hide the persistent offscreen QtMeshDepthCameraNode from the Inspector (added to Manager::isForbiddenNodeName).

Testing

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multi-view AI texture baking that projects front and back views onto the mesh UV0 atlas for improved coverage and consistency.
    • Added “Bake front + back (projects onto UV map)” option to the texture generation UI (shown when mesh conditioning is available).
  • Bug Fixes

    • Reduced repeated legacy cloud credential prompts by recording a one-time migration completion flag and avoiding repeated legacy probing.
  • Other

    • Improved filtering of a depth-related scene node during processing.

fernandotonon and others added 6 commits June 16, 2026 01:33
…ojection baker

First slice of the multi-view AI texture bake (see
src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md). Establishes the reprojection pipeline:
generate per-view images, then project them onto the mesh's FIXED UV0 atlas
(not "recompute UVs from the image").

- MeshDepthRenderer: add renderDepthMapView(entity, size, View) returning the
  depth image PLUS the exact view/projection matrices + camera dir used, so the
  baker re-projects through the identical camera. Built-in front/back/left/
  right/top/bottom views; camera now positions along view.dir with an up hint
  (top/bottom use Z-up). renderDepthMap() stays as the front-view delegate so
  issue #403 is unchanged.
- MultiViewTextureBaker (new, pure-data): for each triangle, per view compute a
  facing weight (max(0,-n·viewDir)^power, back-face/grazing culled), project the
  verts to viewport UV, rasterize in UV0 space, sample the view image at the
  barycentric-interpolated screen UV, accumulate color*weight; resolve by weight
  and seam-dilate (reuses VertexColorBaker::dilate + TexturePaintBuffer).
- Unit tests: synthetic ±Z quad + solid front/back images assert front paints
  red, back-only is culled, front+back blends by facing, dilation fills margins,
  empty-input guards. No Ogre scene / GL / QApplication needed.

Validated the math locally via a standalone harness (front-facing quad → full
64x64 atlas, center = red, back view correctly culled).

Slice 2 will wire fromEntity() + the front+back orchestration in MaterialEditorQML.

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

Wires the slice-1 reprojection baker to the live SD path and the UI.

- MultiViewTextureBaker::fromEntity(): extract world-space triangles + UV0 from
  an Ogre::Entity (via EditableMesh::loadFromEntity), transforming positions by
  the parent node's derived world matrix and normals by its inverse-transpose.
- MaterialEditorQML::generateMeshTextureMultiView(prompt, w, h, strength, views):
  a state machine (MultiViewBakeState pimpl) that renders each view's depth +
  camera matrices, generates one image per view sequentially through the SD
  worker, and on the final completion runs MultiViewTextureBaker::bake over the
  accumulated images and applies the baked atlas to the entity's diffuse via the
  existing applyTextureToEntityDiffuse path. onSDGenerationCompleted routes
  multi-view completions; onSDGenerationError aborts the in-flight bake. Defaults
  to {front, back}. ENABLE_STABLE_DIFFUSION-guarded.
- ~MaterialEditorQML() defaulted in the .cpp so the unique_ptr pimpl sees the
  complete MultiViewBakeState type.
- QML: "Bake front + back (projects onto UV map)" sub-toggle under the existing
  "use selected mesh" checkbox; runTextureGeneration routes to the multi-view
  path when both are checked.

App builds + launches clean (no QML errors). The pure-data baker is covered by
the slice-1 unit tests; the SD-driven orchestration is #ifdef-guarded and not
unit-tested (no model in CI), matching the #403 pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The keychain prompt was back: migrateLegacySettingsIfNeeded() falls through to
readLegacySecretStore() (SecItemCopyMatching / CredRead) whenever QSettings has
no token — which is every launch while signed out. It's called repeatedly at
startup (CloudAccountMenuButton::refresh and others), so macOS prompted again on
each run, defeating the whole point of moving the session to QSettings.

Gate the legacy probe behind a persisted Cloud/legacyMigrationDone flag: probe
the OS secret store AT MOST ONCE ever (to carry a pre-QSettings session forward),
set the flag, and never touch the keychain again — signed in or out. Sign-out
(clearSession) does not reset the flag, so logging out can't re-trigger a probe.

Test: LegacyMigrationProbesOnlyOnce asserts the flag is set after the first call
and a legacy file written afterward is ignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MeshDepthRenderer creates a persistent offscreen camera node named
"QtMeshDepthCameraNode" (it's cached + reused across depth renders). It was
showing up in the Inspector scene tree because isForbiddenNodeName only filtered
unnamed/grid/gizmo nodes. Add it to the forbidden-node list (same canonical
filter SceneTreeModel uses).

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

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23e51973-f56b-47f2-a15b-76560e55a8e6

📥 Commits

Reviewing files that changed from the base of the PR and between 13a5f63 and 681f1ac.

📒 Files selected for processing (1)
  • src/MaterialEditorQML.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/MaterialEditorQML.cpp

📝 Walkthrough

Walkthrough

Introduces a multi-view depth-conditioned AI texture baking pipeline: MeshDepthRenderer gains a view-parameterized renderDepthMapView returning depth image plus camera/view/projection matrices, a new MultiViewTextureBaker reprojects per-view SD images onto a mesh's UV0 atlas using facing-weighted blending and dilation, and MaterialEditorQML orchestrates sequential per-view generation and final bake. A QML checkbox exposes front/back baking. Separately, CloudCredentialStore adds a persistent one-time migration flag to prevent repeated OS keychain probing.

Changes

Multi-View Texture Baking Pipeline

Layer / File(s) Summary
MultiViewTextureBaker data contracts and implementation
src/MultiViewTextureBaker.h, src/MultiViewTextureBaker.cpp
Defines Triangle, View, Options, Report structs; implements projection math helpers, UV-space rasterization/accumulation/dilation bake pipeline, and fromEntity geometry extraction with world-space transforms and UV0 validation.
MultiViewTextureBaker headless tests and CMake wiring
src/MultiViewTextureBaker_test.cpp, src/CMakeLists.txt
Pure-data GoogleTest suite covering rejection of empty/invalid inputs, front-view painting, back-view culling, front+back blending with facing weights, and dilation hole-filling; wires MultiViewTextureBaker into CMake build.
MeshDepthRenderer view-aware refactor
src/MeshDepthRenderer.h, src/MeshDepthRenderer.cpp
Adds View struct (direction/up/name) with factory helpers, introduces RenderResult containing depth image plus view/projection matrices and camera vectors, refactors renderDepthMap into a back-compat front-view wrapper.
MaterialEditorQML multi-view orchestration
src/MaterialEditorQML.h, src/MaterialEditorQML.cpp
Declares generateMeshTextureMultiView Q_INVOKABLE with optional views list, introduces MultiViewBakeState pimpl, declares startNextMultiViewGeneration/finishMultiViewBake helpers; implements state init/validation, per-view depth render + camera matrix stashing, sequential SD dispatch/image capture, final bake + PNG write + Ogre registration + diffuse apply, and error abort.
QML UI checkbox, Manager guard, and design documentation
qml/TexturePropertiesPanel.qml, src/Manager.cpp, src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md
Adds "Bake front + back" checkbox dispatching generateMeshTextureMultiView; excludes QtMeshDepthCameraNode from scene enumeration; includes design document covering pipeline architecture, risks/mitigations, testing strategy, and phased implementation.

Cloud Credential Store One-Time Migration

Layer / File(s) Summary
AppSettingsKeys key + migrateLegacySettingsIfNeeded guard + test
src/AppSettingsKeys.h, src/CloudCredentialStore.cpp, src/CloudCredentialStore_test.cpp
Adds cloudLegacyMigrationDone QSettings accessor; rewrites migrateLegacySettingsIfNeeded to check and set a persistent flag so the OS secret store is probed at most once; new test verifies the guard prevents re-migration.

Sequence Diagram(s)

sequenceDiagram
  participant QML as TexturePropertiesPanel
  participant MaterialEditorQML
  participant MeshDepthRenderer
  participant SDManager
  participant MultiViewTextureBaker

  QML->>MaterialEditorQML: generateMeshTextureMultiView(prompt, w, h, controlStrength, ["front","back"])
  MaterialEditorQML->>MaterialEditorQML: validate SD/model/mesh<br/>init MultiViewBakeState
  loop for each view (front, then back)
    MaterialEditorQML->>MeshDepthRenderer: renderDepthMapView(entity, size, view)
    MeshDepthRenderer-->>MaterialEditorQML: RenderResult(depth, viewMatrix, projMatrix)
    MaterialEditorQML->>SDManager: generateMeshTexture(prompt, depthImagePath)
    SDManager-->>MaterialEditorQML: onSDGenerationCompleted(image)
    MaterialEditorQML->>MaterialEditorQML: load image into view slot<br/>advance index or finish
  end
  MaterialEditorQML->>MultiViewTextureBaker: fromEntity(entity)
  MultiViewTextureBaker-->>MaterialEditorQML: std::vector<Triangle>
  MaterialEditorQML->>MultiViewTextureBaker: bake(triangles, views, buffer, opts)
  MultiViewTextureBaker-->>MaterialEditorQML: Report
  MaterialEditorQML->>MaterialEditorQML: write PNG to generated_textures/<br/>register Ogre resource<br/>apply to entity diffuse
  MaterialEditorQML->>QML: emit textureNameChanged, sdTextureGenerated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • fernandotonon/QtMeshEditor#717: This PR directly extends the mesh-aware ControlNet pipeline from #717 by refactoring MeshDepthRenderer to view-parameterized rendering and adding generateMeshTextureMultiView as a multi-view generalization of single-view mesh texture generation.
  • fernandotonon/QtMeshEditor#201: Both PRs modify MaterialEditorQML's SD generation completion flow (onSDGenerationCompleted, onSDGenerationError) and texture generation UX in TexturePropertiesPanel.qml, so this PR's multi-view bake integration is interwoven with the crash/finalization changes in that PR.

Poem

🐇 Hop, hop! From front and back I peer,
Each view a depth-map crystal clear.
With facing weights I rasterize,
Blend the atlas with accum-eyes.
No keychain prompt shall twice appear—
Migration done, my friend so dear! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.56% 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 clearly and concisely describes the main feature: multi-view AI texture baking that projects images onto UV0, which is the primary change across the entire changeset.
Description check ✅ Passed The PR description provides comprehensive technical details covering both implementation slices, incidental fixes, and testing approach, but does not follow the repository's template structure with explicit Summary/Technical Details sections.
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/multiview-texture-bake

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.

@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: 593e27bb73

ℹ️ 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/MeshDepthRenderer.cpp
Ogre::Vector3 dir = view.dir;
if (dir.isZeroLength()) dir = Ogre::Vector3(0, 0, -1);
dir.normalise();
const Ogre::Vector3 camPos = center - dir * dist;

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 Keep the front depth view on the -Z side

For the existing mesh-conditioned path, renderDepthMap() now delegates to front(), but with front().dir == (0,0,-1) this new placement computes center - dir * dist, which puts the camera on +Z. The previous implementation explicitly used center + (0,0,-dist) because +Z captures the model's back for Mixamo-style meshes, so single-view generation now conditions on the back side and multi-view labels front/back swapped for the default workflow.

Useful? React with 👍 / 👎.

Comment thread src/MaterialEditorQML.cpp
QStringLiteral("entity=%1 views=%2 size=%3")
.arg(st->entityName).arg(viewNames.join(',')).arg(std::max(st->width, st->height)));

m_multiViewBake = std::move(st);

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 Clear multi-view state when generation is stopped

Once this state is installed, clicking the existing Stop button only calls SDManager::stopGeneration(), and onSDGenerationStopped() clears m_sdMeshTextureEntity but never resets m_multiViewBake. In that stopped scenario the next multi-view request is rejected as already in progress, and a later plain SD completion can be consumed by the stale multi-view branch.

Useful? React with 👍 / 👎.

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

Caution

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

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

4508-4514: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the multi-view bake state when SD generation is stopped.

onSDGenerationStopped() clears only the single-view mesh target. If the user stops during a multi-view bake, m_multiViewBake remains set, blocking future bakes and letting a later unrelated SD completion enter the multi-view branch.

🧹 Proposed stop-state cleanup
 void MaterialEditorQML::onSDGenerationStopped()
 {
     m_sdMeshTextureEntity.clear();
+    m_multiViewBake.reset();
     m_sdGenerationProgress = 0.0f;
     emit sdGenerationProgressChanged();
     emit sdIsGeneratingChanged();
🤖 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/MaterialEditorQML.cpp` around lines 4508 - 4514, In the
onSDGenerationStopped() function in MaterialEditorQML.cpp, the multi-view bake
state flag m_multiViewBake is not being cleared when SD generation is stopped.
This causes the flag to remain set if the user stops during a multi-view bake,
blocking future bakes and potentially allowing unrelated SD completions to
incorrectly enter the multi-view branch. Add a statement to clear or reset
m_multiViewBake (likely to false or an equivalent cleared state) alongside the
existing cleanup that clears m_sdMeshTextureEntity and resets
m_sdGenerationProgress.
🤖 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/CloudCredentialStore_test.cpp`:
- Around line 154-180: The test LegacyMigrationProbesOnlyOnce is
environment-dependent because it calls migrateLegacySettingsIfNeeded() on line
159 without first ensuring the legacy cloud_session.dat file doesn't exist. If a
stale file remains from a previous test run, the first migration may behave
unexpectedly. Before the first migrateLegacySettingsIfNeeded() call, add code to
remove the legacy file at the path constructed from
QStandardPaths::writableLocation and the cloud_session.dat filename (similar to
how it is constructed later in the test) to guarantee clean preconditions.

In `@src/CloudCredentialStore.cpp`:
- Around line 195-223: Add Sentry breadcrumbs to the CloudCredentialStore legacy
migration operation to improve field diagnosability. Use
SentryReporter::addBreadcrumb() at key points in the migration flow: when
checking if migration is already done, before reading from the legacy secret
store in readLegacySecretStore(), when successfully writing migrated credentials
via writeToSettings(), and after marking the migration complete by setting
cloudLegacyMigrationDone(). Use the category 'file.import' for the legacy store
read operation and appropriate descriptive messages for each breadcrumb to track
the migration state transitions.

In `@src/MaterialEditorQML.cpp`:
- Around line 4085-4087: The current breadcrumb logging uses only the
ai.assist.mesh_texture_multiview category for both the multi-view generation
trigger and file write operations. According to coding guidelines, you need to
add separate breadcrumbs with appropriate categories: use ui.action category for
the multi-view generation trigger (user-facing action) and add a new file.export
category breadcrumb for the baked PNG write operation (file I/O). Update the
SentryReporter::addBreadcrumb calls to use these distinct categories based on
the type of operation being logged, and ensure both operations are properly
tracked with their respective category types.
- Around line 4183-4189: The entity name from s->entityName is being used
directly in the filename construction for multiview_bake_*.png without
sanitization, which can cause save failures due to special characters or path
separators. Sanitize s->entityName by removing or replacing filesystem-unsafe
characters (such as path separators, control characters, and reserved filesystem
names) before passing it to the QStringLiteral().arg() call that builds outName.
- Around line 4378-4384: The multi-view bake completion logic in the if block
checking img.isNull() only handles the success case where the image loads
successfully. When img.isNull() returns true, indicating the output image cannot
be loaded from outputPath, the code silently continues without resetting state,
leaving the baked view without pixels while still advancing s.current. This
causes the final bake to potentially fail late or process invalid input. Add an
else branch that resets m_multiViewBake to null (or clears the
MultiViewBakeState) when img.isNull() is true, ensuring the multi-view baking
process is immediately aborted on any unreadable output path.

In `@src/MeshDepthRenderer.h`:
- Around line 38-56: The View struct and its static methods (front, back, left,
right, top, bottom) have direction vectors that are semantically backwards. The
code in MeshDepthRenderer.cpp uses camPos = center - dir * dist, so the current
directions produce camera positions that capture the opposite side of the mesh
from what's intended. Flip the sign of the direction vectors in the View struct
default initialization and in all six static view methods (front, back, left,
right, top, bottom) to correct the semantics. Additionally, update the
explanatory comment in MeshDepthRenderer.cpp around line 168 to clarify the
corrected camera-side semantics after this fix.

In `@src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md`:
- Line 31: The markdown file has two linting violations: the fenced code block
starting at line 31 is missing a language specifier (MD040), and the table in
the "Risks & mitigations" section lacks blank lines before and after it (MD058).
Add "text" as the language identifier to the opening fence (change the opening
``` to ```text), and add blank lines immediately before and after the table to
satisfy the blanks-around-tables rule. Apply the same fixes at line 119 as
indicated in the consolidated sites.

In `@src/MultiViewTextureBaker.cpp`:
- Around line 261-265: The code at lines 261-265 in MultiViewTextureBaker.cpp
sets an error message in errorOut when UV0 is unusable but continues execution
and returns triangles anyway. Since the caller in MaterialEditorQML.cpp only
checks if the returned triangles are empty to determine failure, this error
condition is silently ignored. Modify the error handling block to return an
empty triangles vector immediately after setting errorOut, so that the caller's
tris.empty() check will properly catch the UV0 usability issue and abort
processing.

---

Outside diff comments:
In `@src/MaterialEditorQML.cpp`:
- Around line 4508-4514: In the onSDGenerationStopped() function in
MaterialEditorQML.cpp, the multi-view bake state flag m_multiViewBake is not
being cleared when SD generation is stopped. This causes the flag to remain set
if the user stops during a multi-view bake, blocking future bakes and
potentially allowing unrelated SD completions to incorrectly enter the
multi-view branch. Add a statement to clear or reset m_multiViewBake (likely to
false or an equivalent cleared state) alongside the existing cleanup that clears
m_sdMeshTextureEntity and resets m_sdGenerationProgress.
🪄 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: 4df38bb8-e76e-4641-9f7c-6558f7d6e358

📥 Commits

Reviewing files that changed from the base of the PR and between 29accd8 and 593e27b.

📒 Files selected for processing (14)
  • qml/TexturePropertiesPanel.qml
  • src/AppSettingsKeys.h
  • src/CMakeLists.txt
  • src/CloudCredentialStore.cpp
  • src/CloudCredentialStore_test.cpp
  • src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md
  • src/Manager.cpp
  • src/MaterialEditorQML.cpp
  • src/MaterialEditorQML.h
  • src/MeshDepthRenderer.cpp
  • src/MeshDepthRenderer.h
  • src/MultiViewTextureBaker.cpp
  • src/MultiViewTextureBaker.h
  • src/MultiViewTextureBaker_test.cpp

Comment thread src/CloudCredentialStore_test.cpp
Comment on lines +195 to 223
// The OS secret store must be probed AT MOST ONCE, ever. Each probe is a
// SecItemCopyMatching (macOS Keychain) / CredRead (Windows) that can raise a
// confirmation prompt — re-probing on every launch (which happens when the
// user is signed out, since QSettings then has no token) is exactly the
// repeated-keychain-prompt bug we set out to kill. Persist a "done" flag the
// first time through and never touch the keychain again, signed in or not.
{
QSettings settings;
if (settings.value(AppSettingsKeys::cloudLegacyMigrationDone(), false).toBool())
return;
}

// If a session already lives in QSettings there is nothing to migrate, but
// still mark the probe done so we don't keychain-probe on a later sign-out.
if (!readFromSettings().hasToken()) {
// One-time upgrade: a user who signed in on a pre-QSettings build has
// their token in the OS secret store / legacy fallback file. Carry it
// into QSettings so they stay signed in across the backend change.
const CloudSession legacy = readLegacySecretStore();
if (legacy.hasToken() && writeToSettings(legacy)) {
g_sessionCache = legacy;
g_cacheValid = true;
}
}

QSettings settings;
settings.setValue(AppSettingsKeys::cloudLegacyMigrationDone(), true);
settings.sync();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add breadcrumbs for the one-time migration path.

Line 195–223 performs a significant operation (legacy probe/migration + persistence) but emits no Sentry breadcrumbs, which weakens field diagnosability of prompt-related regressions.

Proposed patch
+#include "SentryReporter.h"
@@
 void CloudCredentialStore::migrateLegacySettingsIfNeeded()
 {
+    SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                  QStringLiteral("cloud_legacy_migration_enter"));
     // The OS secret store must be probed AT MOST ONCE, ever. Each probe is a
@@
     {
         QSettings settings;
-        if (settings.value(AppSettingsKeys::cloudLegacyMigrationDone(), false).toBool())
+        if (settings.value(AppSettingsKeys::cloudLegacyMigrationDone(), false).toBool()) {
+            SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                          QStringLiteral("cloud_legacy_migration_skip_done"));
             return;
+        }
     }
@@
         const CloudSession legacy = readLegacySecretStore();
         if (legacy.hasToken() && writeToSettings(legacy)) {
             g_sessionCache = legacy;
             g_cacheValid = true;
+            SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                          QStringLiteral("cloud_legacy_migration_success"));
+        } else if (legacy.hasToken()) {
+            SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                          QStringLiteral("cloud_legacy_migration_write_failed"));
+        } else {
+            SentryReporter::addBreadcrumb(QStringLiteral("file.import"),
+                                          QStringLiteral("cloud_legacy_migration_no_legacy_data"));
         }
     }
@@
     settings.setValue(AppSettingsKeys::cloudLegacyMigrationDone(), true);
     settings.sync();
+    SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
+                                  QStringLiteral("cloud_legacy_migration_mark_done"));
 }

As per coding guidelines, "Add Sentry breadcrumbs for all user-facing actions and significant operations using SentryReporter::addBreadcrumb() with categories: 'ui.action' for toolbar/menu clicks, 'ai.tool_call' for MCP tools, 'file.import'/'file.export' for I/O".

🤖 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/CloudCredentialStore.cpp` around lines 195 - 223, Add Sentry breadcrumbs
to the CloudCredentialStore legacy migration operation to improve field
diagnosability. Use SentryReporter::addBreadcrumb() at key points in the
migration flow: when checking if migration is already done, before reading from
the legacy secret store in readLegacySecretStore(), when successfully writing
migrated credentials via writeToSettings(), and after marking the migration
complete by setting cloudLegacyMigrationDone(). Use the category 'file.import'
for the legacy store read operation and appropriate descriptive messages for
each breadcrumb to track the migration state transitions.

Source: Coding guidelines

Comment thread src/MaterialEditorQML.cpp
Comment thread src/MaterialEditorQML.cpp
Comment thread src/MaterialEditorQML.cpp
Comment thread src/MeshDepthRenderer.h Outdated
Comment thread src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md Outdated

## Pipeline

```

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

Fix markdownlint warnings for the fenced block and table spacing.

This file currently triggers MD040 and MD058 (fenced-code-language, blanks-around-tables).

🧹 Proposed fix
-```
+```text
 selected Entity
   │
   ├─ 0. UV gate: UvUnwrap::infoForEntity → if !hasUv0 || uv0Coverage < THRESH
@@
-## Risks & mitigations
+## Risks & mitigations
+
 | Risk | Mitigation |
 |---|---|
 | Occlusion / back-facing bleed | facing weight `max(0,−n·viewDir)` + per-view depth occlusion test |
 | Front/back style mismatch | same seed + prompt; (later) img2img-condition back on front |
 | Side seam with only 2 views | feathered facing blend + dilate; document that 4 views removes it |
 | Bad/missing UV0 | auto-unwrap gate (UvUnwrap) before bake |
 | Live skinned-mesh buffer mutation | bake reads geometry read-only; unwrap uses the GUI-safe `unwrapEntityToFile` snapshot/restore path |
 | RTSS apply unreliability (`#403`) | bake targets real UV0 atlas → `applyTextureToEntityDiffuse` binds a normal diffuse_map |
+
 ## Test plan (headless, CI Linux+Xvfb; ASSERT_TRUE(tryInitOgre), no GTEST_SKIP)

Also applies to: 119-119

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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/MULTIVIEW_TEXTURE_BAKE_DESIGN.md` at line 31, The markdown file has two
linting violations: the fenced code block starting at line 31 is missing a
language specifier (MD040), and the table in the "Risks & mitigations" section
lacks blank lines before and after it (MD058). Add "text" as the language
identifier to the opening fence (change the opening ``` to ```text), and add
blank lines immediately before and after the table to satisfy the
blanks-around-tables rule. Apply the same fixes at line 119 as indicated in the
consolidated sites.

Source: Linters/SAST tools

Comment thread src/MultiViewTextureBaker.cpp Outdated
…ustness

- MeshDepthRenderer: FLIP the built-in view directions (Codex P1 / CodeRabbit
  major). With camPos = center - dir*dist, front must be dir=(0,0,1) to place the
  camera on -Z (the front for -Z-facing imports); the previous (0,0,-1) put it on
  +Z and captured the BACK, regressing the #403 single-view path and swapping
  front/back in multi-view. All six views flipped + comments corrected. This also
  fixes the camDirection fed to the baker's facing cull.
- MultiViewTextureBaker::fromEntity: return EMPTY when the mesh has no usable UV0
  (was returning triangles + only setting errorOut, so the caller baked garbage).
- MaterialEditorQML multi-view: abort the run if a per-view image fails to load
  (was advancing with a null image → late bake failure); clear m_multiViewBake in
  onSDGenerationStopped so Stop can't strand it; sanitize the entity name into a
  filesystem-safe bake filename; emit ui.action + file.export breadcrumbs.
- Design doc: markdownlint (fenced-code language + table blank line).

Skipped: adding Sentry breadcrumbs inside CloudCredentialStore (CodeRabbit) — the
store is intentionally dependency-free (QSettings/QFile/Security only); coupling
it to SentryReporter for a low-value breadcrumb isn't worth it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Addressed the review feedback in 13a5f63:

  • Front depth view on the -Z side (Codex P1) / flip built-in view directions (CodeRabbit major): correct — with camPos = center - dir*dist, front must be dir=(0,0,1) to place the camera on -Z (the front for -Z-facing imports). The previous (0,0,-1) put it on +Z and captured the back, regressing the AI: Mesh-aware texture generation (depth-conditioned sd.cpp) #403 single-view path and swapping front/back in multi-view. Flipped all six views + comments; this also fixes the camDirection fed to the baker's facing cull.
  • Return failure when UV0 is unusable (CodeRabbit major): fromEntity now returns an empty triangle list (not just sets errorOut) so the caller aborts instead of baking onto degenerate UVs.
  • Abort if a per-view image can't be loaded (CodeRabbit major): onSDGenerationCompleted now resets the multi-view state and errors immediately on a null QImage rather than advancing with empty pixels.
  • Clear multi-view state on Stop (Codex P2): onSDGenerationStopped now resets m_multiViewBake.
  • Breadcrumb categories (CodeRabbit): added ui.action on the trigger and file.export on the baked-PNG write.
  • Sanitize entity name for filename (CodeRabbit): baked PNG basename is now [^A-Za-z0-9._-] → _ sanitized.
  • Test stabilization + markdownlint (CodeRabbit): clear the legacy cloud_session.dat before the first migrate in the one-time-probe test; tagged the doc's ASCII fence as text and added the table blank line.

Skipped: adding Sentry breadcrumbs inside CloudCredentialStore — the store is intentionally dependency-free (QSettings/QFile/Security only); coupling it to SentryReporter for a low-value breadcrumb isn't worth it. The user-facing actions that call it already emit breadcrumbs at the UI layer.

App + UnitTests build clean.

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

Caution

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

⚠️ Outside diff range comments (2)
src/MaterialEditorQML.cpp (2)

4050-4064: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject multi-view starts while any SD job is already running.

m_multiViewBake makes onSDGenerationCompleted() capture the next SD completion, so starting this while another SD generation is in flight can pair an unrelated output with the first bake view. Add an sdManager->isGenerating() guard before creating the multi-view state.

🐛 Proposed guard
     SDManager *sdManager = SDManager::instance();
     if (!sdManager->isModelLoaded()) {
         emit sdGenerationError("No SD model loaded. Please download and load a model from AI Settings.");
         return;
     }
+    if (sdManager->isGenerating()) {
+        emit sdGenerationError("Stable Diffusion is already generating. Stop or wait for the current job before starting a multi-view bake.");
+        return;
+    }
     auto* sel = SelectionSet::getSingleton();
🤖 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/MaterialEditorQML.cpp` around lines 4050 - 4064, The multi-view bake
start logic in the validation block needs an additional guard to prevent
starting a multi-view bake while an SD generation is already in flight. Add a
check using sdManager->isGenerating() after the existing
sdManager->isModelLoaded() check and before the m_multiViewBake check, emitting
an appropriate sdGenerationError if a generation is already running, to prevent
the onSDGenerationCompleted() callback from incorrectly pairing unrelated SD
outputs with the first bake view.

4065-4074: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when the selected mesh cannot bake to UV0.

finishMultiViewBake() rejects empty fromEntity() results only after all view images have been generated. Preflight the same UV0/triangle extraction here so meshes without usable UV0 fail before spending multiple SD runs.

⚡ Proposed preflight
     }
     Ogre::Entity* entity = entities.first();
+
+    QString geoErr;
+    if (MultiViewTextureBaker::fromEntity(entity, &geoErr).empty()) {
+        emit sdGenerationError(QStringLiteral("Bake failed: %1").arg(geoErr));
+        return;
+    }
 
     // LCOV_EXCL_START — requires a loaded SD model + a mesh
     auto st = std::make_unique<MultiViewBakeState>();
🤖 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/MaterialEditorQML.cpp` around lines 4065 - 4074, Add a preflight
validation check for UV0/triangle extraction immediately after obtaining the
entity from `entities.first()` and before populating the `MultiViewBakeState`
object. This check should verify that the mesh has usable UV0 coordinates using
the same validation logic that `finishMultiViewBake()` performs later. If the
UV0 validation fails, return early to avoid wasting computational resources on
multiple Stable Diffusion runs. The check should happen before the
`std::make_unique<MultiViewBakeState>()` call to fail fast for meshes without
usable UV0.
🤖 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/MaterialEditorQML.cpp`:
- Around line 4203-4204: The SentryReporter::addBreadcrumb call at the specified
location logs the full outPath which contains absolute local paths that may
include sensitive information like usernames or project paths. Modify the
breadcrumb to log only the baked filename instead of the complete outPath by
extracting just the filename component, while keeping the "file.export"
breadcrumb category and descriptive message format.

---

Outside diff comments:
In `@src/MaterialEditorQML.cpp`:
- Around line 4050-4064: The multi-view bake start logic in the validation block
needs an additional guard to prevent starting a multi-view bake while an SD
generation is already in flight. Add a check using sdManager->isGenerating()
after the existing sdManager->isModelLoaded() check and before the
m_multiViewBake check, emitting an appropriate sdGenerationError if a generation
is already running, to prevent the onSDGenerationCompleted() callback from
incorrectly pairing unrelated SD outputs with the first bake view.
- Around line 4065-4074: Add a preflight validation check for UV0/triangle
extraction immediately after obtaining the entity from `entities.first()` and
before populating the `MultiViewBakeState` object. This check should verify that
the mesh has usable UV0 coordinates using the same validation logic that
`finishMultiViewBake()` performs later. If the UV0 validation fails, return
early to avoid wasting computational resources on multiple Stable Diffusion
runs. The check should happen before the
`std::make_unique<MultiViewBakeState>()` call to fail fast for meshes without
usable UV0.
🪄 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: eef531a9-83cb-4279-9779-9dbdafb1dde7

📥 Commits

Reviewing files that changed from the base of the PR and between 593e27b and 13a5f63.

📒 Files selected for processing (6)
  • src/CloudCredentialStore_test.cpp
  • src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md
  • src/MaterialEditorQML.cpp
  • src/MeshDepthRenderer.cpp
  • src/MeshDepthRenderer.h
  • src/MultiViewTextureBaker.cpp
✅ Files skipped from review due to trivial changes (1)
  • src/MULTIVIEW_TEXTURE_BAKE_DESIGN.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/CloudCredentialStore_test.cpp
  • src/MeshDepthRenderer.cpp
  • src/MultiViewTextureBaker.cpp

Comment thread src/MaterialEditorQML.cpp Outdated
…crumb (#729)

CodeRabbit: the file.export breadcrumb logged the full app-data outPath, which
embeds the local username/path — PII in telemetry. Log outName (basename) only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Addressed the absolute-path breadcrumb finding in abdc81e: the file.export breadcrumb now logs the baked-texture basename (outName) instead of the absolute outPath, so no local username/path leaks into telemetry.

@sonarqubecloud

Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit 674bc32 into master Jun 16, 2026
21 checks passed
@fernandotonon fernandotonon deleted the feat/multiview-texture-bake branch June 16, 2026 23:16
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