Skip to content

feat(lights): scene light authoring epic (#482–#487)#803

Merged
fernandotonon merged 7 commits into
masterfrom
feat/lights-epic-482-487
Jul 4, 2026
Merged

feat(lights): scene light authoring epic (#482–#487)#803
fernandotonon merged 7 commits into
masterfrom
feat/lights-epic-482-487

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Test plan

  • ./build_local/bin/UnitTests --gtest_filter="Light*" (25 tests)
  • Apply each preset rig; confirm distinct lighting + matching skybox (studio / sunset / overcast / indoor)
  • Repeat Apply preset rig with replace on/off — no glow stacking, skybox texture stays intact
  • Undo/redo rig apply; pick lights via viewport gizmos; toggle View → Show Light Icons

Closes #483, #484, #485, #486, #487

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added a dedicated Light inspector in the Properties panel, including mixed (“mixed” indicators) editing, linked diffuse/specular colors, attenuation/spot controls, and slider-based undoable edits.
    • Added preset rig lighting with a rig picker, optional replace-existing behavior, and support for recommended HDRI.
    • Added full light management in the scene tree (rename, duplicate, delete, and a light-specific context menu).
    • Added viewport toggles for light icons and “selected light gizmos only”.
  • Bug Fixes

    • Improved morph target weight readouts so values refresh correctly after non-drag updates.
  • UI/UX

    • Default-collapsed “Decimate (single-pass)” and “LOD Generation” panels for a cleaner Properties panel.

Add user scene lights with outliner integration, inspector properties, viewport
gizmos/icons, preset rigs (three-point default), and undoable apply/replace flows.
Pair outdoor/indoor/studio rigs with bundled HDRIs without reloading when already active.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fernandotonon, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfd3cd50-b475-4e62-8f9e-f1182b2e2c8b

📥 Commits

Reviewing files that changed from the base of the PR and between 7933c00 and d62720e.

📒 Files selected for processing (11)
  • qml/PropertiesPanel.qml
  • src/LightManager.cpp
  • src/LightRigLibrary.cpp
  • src/LightRigLibrary.h
  • src/LightVisualizer.cpp
  • src/LightVisualizer_test.cpp
  • src/SceneLightingController.cpp
  • src/commands/LightCommands.cpp
  • src/commands/LightCommands.h
  • src/commands/LightCommands_test.cpp
  • src/mainwindow.cpp
📝 Walkthrough

Walkthrough

This PR adds a full lighting subsystem: user-light management, preset rig application, light property editing, viewport light overlays, scene-tree integration, and QML/UI wiring for light creation and editing.

Changes

Lighting Subsystem

Layer / File(s) Summary
LightManager foundation
src/LightManager.{h,cpp}, src/LightManager_test.cpp, src/GlobalDefinitions.h, src/Manager.cpp, src/CMakeLists.txt
Defines LightHandle/LightSnapshot, implements LightManager singleton CRUD/snapshot behavior, adds light query flags, and wires Manager default lighting and lifecycle cleanup.
Rig presets and undo commands
src/LightRigLibrary.{h,cpp}, src/LightRigLibrary_test.cpp, src/commands/LightCommands.{h,cpp}, src/commands/LightCommands_test.cpp, src/SceneLightingController.{h,cpp}, src/AppSettingsKeys.h, src/CMakeLists.txt, tests/CMakeLists.txt
Implements preset rigs, default-rig persistence, rig application results, undo commands for light and ambient changes, and the rig-selection controller API.
Light property editing
src/LightPropertiesController.{h,cpp}, src/LightPropertiesController_test.cpp, qml/PropertiesPanel.qml
Declares and implements light property editing, mixed-state handling, slider undo batching, and the Light inspector QML section.
Visual overlay and tree integration
src/LightVisualizer.{h,cpp}, src/LightVisualizer_test.cpp, src/SceneTreeModel.{h,cpp}, src/SceneTreeNode.qml, src/PropertiesPanelController.{h,cpp}, src/LightsController.{h,cpp}, src/TransformOperator.cpp
Adds viewport icons/gizmos, Light tree handling, rename/delete routing, viewport picking, and light-only selection/duplication behavior.
Main window wiring and QML surface
src/mainwindow.{h,cpp}, src/mainwindow_test.cpp, ui_files/mainwindow.ui, qml/PropertiesPanel.qml, src/PropertiesPanel_qml_test.cpp
Registers lighting singletons, adds toolbar/menu actions, wires LightVisualizer visibility controls, updates teardown, and adds the lighting controls in the Properties panel.
Scene lighting UI
src/SceneLightingController.{h,cpp}, qml/PropertiesPanel.qml
Exposes preset rig selection and replace-existing controls and wires them into the Properties panel mode tools.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainWindow
  participant LightsController
  participant LightManager
  participant UndoManager
  MainWindow->>LightsController: addPointLightAtViewport()
  LightsController->>LightManager: createLightAt(type, position, direction)
  LightsController->>UndoManager: push CreateLightCommand
  LightsController-->>MainWindow: select created light
Loading
sequenceDiagram
  participant SceneLightingController
  participant LightRigLibrary
  participant LightManager
  participant UndoManager
  SceneLightingController->>LightRigLibrary: apply(rigId, replaceExisting)
  LightRigLibrary->>LightManager: capture/remove/apply rig lights
  LightRigLibrary-->>SceneLightingController: LightRigApplyResult
  SceneLightingController->>UndoManager: push ApplyLightRigCommand
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing required template sections like Technical Details, Features, and Bugfixes, so it is not aligned with the repo template. Add the missing template sections and briefly fill in technical details, features, and bugfixes; keep the existing Summary and Test plan.
Linked Issues check ⚠️ Warning The PR adds substantial GUI and controller work beyond #483, which explicitly scoped this slice to LightManager foundation with no GUI. Restrict this slice to LightManager foundation and default-light replacement, or split the GUI, outliner, and inspector work into the later linked issues.
Out of Scope Changes check ⚠️ Warning The PR includes extensive GUI, controller, and visualizer changes that are outside #483's foundation-only scope and no-GUI requirement. Move the inspector, outliner, visualization, and rig UI changes to the later light-epic slices, leaving this PR to the foundation work.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main lighting-authoring epic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lights-epic-482-487

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.

@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: 3407eb095c

ℹ️ 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 +174 to +175
m_after = cmd->m_after;
return true;

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 light edit merges scoped to the same lights

When two consecutive edits change the same property class on different selections, QUndoStack can merge them because the id only encodes the property class. For example, editing Light A's intensity and then Light B's intensity leaves m_before for A but replaces m_after with B here, so Undo reverts A while B's edit remains applied, and Redo applies only B. The merge needs to reject commands whose snapshot names/counts do not match the existing command.

Useful? React with 👍 / 👎.

Comment thread src/commands/LightCommands.cpp Outdated
Comment on lines +199 to +200
for (const LightSnapshot& snapshot : m_removedLights)
lights->restoreSnapshot(snapshot);

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 Preserve rig membership when undoing rig apply

Undoing a rig apply restores the lights that were removed from previous rig groups as ordinary root-level lights. If a user applies Three-point Studio without replacing over an existing Single Key rig, Undo restores the old KeyLight outside any rig group; Redo with replaceExisting == false then keeps that restored light and creates KeyLight1, and the next Undo deletes the restored light instead of the redone rig light. The command needs to restore the removed rig group/parent relationship or replay the exact captured snapshots instead of relying on current rig-group discovery.

Useful? React with 👍 / 👎.

Comment thread src/SceneLightingController.cpp Outdated
Comment on lines +143 to +144
UndoManager::getSingleton()->push(new ApplyLightRigCommand(result));
maybeLoadSuggestedHdri(result.suggestedHdri);

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 Include suggested HDRI changes in rig undo

The rig application command is pushed before loading the suggested HDRI, so the HDRI/skybox change is outside the undoable state. Applying, for example, Outdoor Sunset over a Studio environment and then pressing Undo restores only lights and ambient; the sunset skybox remains active, and Redo from the undo path calls LightRigLibrary::apply() without reloading the suggested HDRI. Capture the previous/current environment in the command or keep the HDRI load out of the undoable rig apply.

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

Caution

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

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

128-141: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Move the LightManager connection out of the Manager constructor src/Manager.cpp:137
Manager::m_pSingleton is still null while this constructor runs, so LightManager::tryConnectToManager() returns before wiring sceneNodeDestroyed. Connect it after new Manager(parent) returns, or in a later init step.

🤖 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/Manager.cpp` around lines 128 - 141, Move the LightManager hookup out of
Manager::Manager so it runs only after Manager::m_pSingleton has been assigned.
The current call to LightManager::getSingleton()->tryConnectToManager() happens
too early in the constructor, causing the connection logic to return before
sceneNodeDestroyed is wired; remove that call from Manager::Manager and perform
it after new Manager(parent) completes or in a later initialization method that
runs once the singleton is ready.
src/commands/LightCommands_test.cpp (1)

1-90: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

No tests for ApplyLightRigCommand or SetSceneAmbientCommand.

Both new command classes lack any coverage here. Adding an undo→redo→undo cycle test for ApplyLightRigCommand in particular would have caught the stale-state issue flagged in LightCommands.cpp (redo() after undo doesn't refresh m_addedLights/m_rigGroupNodeName).

As per coding guidelines: "Add Google Test unit tests for new functionality, and keep test files alongside source in src/ with the _test.cpp suffix."

Want me to draft ApplyLightRigCommand/SetSceneAmbientCommand tests, including a redo-after-undo-after-redo cycle that exercises the identified staleness bug?

🤖 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/commands/LightCommands_test.cpp` around lines 1 - 90, The test suite in
LightCommandsOgreTest only covers create/delete, duplicate, and rename flows, so
add coverage for the new ApplyLightRigCommand and SetSceneAmbientCommand. Extend
this test file with Google Test cases that exercise undo→redo→undo behavior for
ApplyLightRigCommand, specifically verifying redo after undo refreshes state and
does not reuse stale m_addedLights or m_rigGroupNodeName. Also add a basic
undo/redo test for SetSceneAmbientCommand, using the existing LightManager,
UndoManager, and LightsController helpers to validate the command lifecycle.

Source: Coding guidelines

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

338-342: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rename result is ignored — see root-cause note in LightsController.h.

renameLight(...) returns void upstream, so this always emits selectionChanged() even if the rename was rejected (empty/duplicate name). Fix at the source (LightsController::renameLight / LightsController.h) so this call site can react to failure.

🤖 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/PropertiesPanelController.cpp` around lines 338 - 342, The rename flow in
PropertiesPanelController::renameSceneTreeLight currently assumes
LightsController::renameLight always succeeds, so selectionChanged() is emitted
even when the rename is rejected. Update LightsController::renameLight in
LightsController.h/.cpp to return a success/failure result for invalid or
duplicate names, then adjust PropertiesPanelController::renameSceneTreeLight to
emit selectionChanged() only on success and handle the failure path
appropriately.
🧹 Nitpick comments (4)
src/PropertiesPanel_qml_test.cpp (1)

9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Working-directory-relative path is fragile across platforms/build layouts.

propertiesPanelQmlPath() assumes the test binary's CWD is exactly one level below the qml/ directory. Multi-config build layouts (e.g. Visual Studio generators placing binaries under Debug//Release/) or CI invoking ctest from a different working directory can break this on Windows even if it works on Linux/macOS CI.

🛠️ Suggested alternative

Consider resolving the path from a compile-time-known source root (e.g. a CMake-configured header/definition, or QFINDTESTDATA) instead of QDir::currentPath().

As per coding guidelines, "Code must compile and run on Windows, Linux (Ubuntu), and macOS."

🤖 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/PropertiesPanel_qml_test.cpp` around lines 9 - 12, The path lookup in
propertiesPanelQmlPath() is using QDir::currentPath(), which makes the test
depend on the process working directory and breaks in different build layouts.
Update this helper to resolve PropertiesPanel.qml from a stable test/data
location instead of the current directory, for example by using a compile-time
configured source/test root or QFINDTESTDATA so the PropertiesPanel_qml_test.cpp
lookup works across Windows, Linux, and macOS. Keep the change localized to
propertiesPanelQmlPath().

Source: Coding guidelines

src/LightRigLibrary.cpp (1)

368-407: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

apply() doesn't clean up an empty rig group on failure, and silently skips failed light creations.

If createLightUnderParent fails for a light spec, the loop just continues without recording the failure; and if it fails for all specs, result.ok becomes false (Line 403-406) but the just-created, tagged rigGroup node (Line 368-375) is never destroyed, leaving orphaned scene state. Currently unreachable since every catalog entry has ≥1 light, but worth hardening for future rigs/failure modes.

♻️ Possible hardening
     mgr->getSceneMgr()->setAmbientLight(spec->ambient);
     result.ambientAfter = spec->ambient;
     result.suggestedHdri = spec->suggestedHdri;
     result.replaceExisting = replaceExisting;
     result.ok = !result.addedLights.isEmpty();

     if (!result.ok)
+    {
         result.error = QStringLiteral("Rig produced no lights");
+        destroyRigGroupNode(result.rigGroupNodeName);
+    }
🤖 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/LightRigLibrary.cpp` around lines 368 - 407, The apply() path in
LightRigLibrary::apply leaves behind a tagged rigGroup node when no lights are
successfully created, and it silently ignores per-light creation failures.
Update the loop around createLightUnderParent to record failed light specs (or
at least surface a failure when none succeed), and if result.ok remains false
after the loop, destroy the rigGroup node before returning. Use the existing
rigGroup, result.addedLights, and result.error handling in apply() to keep the
cleanup and failure reporting localized.
src/LightRigLibrary_test.cpp (1)

1-111: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing test for apply() with an unknown rig id.

LightRigLibrary::apply()'s error path ("Unknown light rig: %1") isn't exercised by any test here.

As per coding guidelines: "Add Google Test unit tests for new functionality".

TEST_F(LightRigLibraryOgreTest, ApplyUnknownRig_ReturnsError)
{
    const LightRigApplyResult result = LightRigLibrary::apply(QStringLiteral("does_not_exist"), true);
    EXPECT_FALSE(result.ok);
    EXPECT_FALSE(result.error.isEmpty());
}

Otherwise the rest of the coverage (catalog size, apply results, ambient, HDRI suggestions, replace/no-replace, idempotent re-apply) looks solid.

🤖 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/LightRigLibrary_test.cpp` around lines 1 - 111, Add a Google Test that
exercises the error path in LightRigLibrary::apply() for an unknown rig id,
since the current suite only covers valid rigs. Use the existing
LightRigLibraryOgreTest fixture and call LightRigLibrary::apply() with a
non-existent id, then assert result.ok is false and result.error is populated to
cover the “Unknown light rig” behavior.

Source: Coding guidelines

src/LightsController.cpp (1)

280-294: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Silent no-op on rename failure.

If findLight(oldName) misses or LightManager::renameLight fails (e.g. duplicate name), the function just returns with no signal/feedback. Downstream, qml/SceneTreeNode.qml's rename TextInput.onEditingFinished (lines 201-206) unconditionally sets treeNode.renaming = false after calling the rename, so a failed rename appears to silently do nothing with no user-visible error.

Consider emitting a renameFailed(QString reason) signal (or returning a status QML can react to) so the UI can surface why the rename didn't apply.

🤖 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/LightsController.cpp` around lines 280 - 294, The rename flow in
LightsController::renameLight currently returns silently when
LightManager::findLight or LightManager::renameLight fails, so the UI cannot
explain why the rename did not apply. Update LightsController::renameLight to
report failure back to QML, either by emitting a renameFailed(QString reason)
signal or by returning a status that qml/SceneTreeNode.qml can inspect, and make
sure the failure paths for missing oldName and renameLight rejection provide a
user-visible reason while preserving the existing success path that pushes
RenameLightCommand and calls selectLightHandle.
🤖 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 `@qml/PropertiesPanel.qml`:
- Around line 3906-3925: The light color swatches in PropertiesPanel.qml are
mouse-only and need keyboard access. Update the Rectangle swatches used for
diffuse and the other color picker so they can receive focus, expose accessible
metadata, and trigger the same pickers from Enter/Space. Use the existing
LightPropertiesController.pickDiffuseColor() and the corresponding second picker
handler as the action targets, and add the keyboard handling to the swatch items
or their MouseArea so keyboard users can open them too.
- Around line 5045-5066: The “Apply preset rig” control in PropertiesPanel.qml
is mouse-only and needs keyboard accessibility. Update the Rectangle/MouseArea
pair for the applyRigMa block so it can receive tab focus, exposes an accessible
button role/name, and triggers SceneLightingController.applySelectedRig() from
keyboard activation as well as click. Keep the existing visual behavior, but
make the control reachable and operable without a mouse.

In `@qml/SceneTreeNode.qml`:
- Around line 90-102: Right-click on non-Light rows is falling through to the
generic selection path in SceneTreeNode.qml because onClicked only special-cases
treeNode.isLightType. Update the onClicked handler to ignore Qt.RightButton
unless the node is a Light with a context menu, and only call
treeModel.selectItem(...) for left-clicks (or other intended selection buttons);
use the existing onClicked, lightContextMenu.popup, treeNode.isLightType, and
treeModel.selectItem symbols to keep the behavior scoped correctly.

In `@src/commands/LightCommands.cpp`:
- Around line 209-218: ApplyLightRigCommand::redo() is leaving the command state
stale after undo/redo cycles because the result of LightRigLibrary::apply() is
ignored on subsequent redos. Update redo() so it refreshes m_addedLights,
m_rigGroupNodeName, and m_removedLights from the new LightRigApplyResult
returned by LightRigLibrary::apply(m_rigId, m_replaceExisting), keeping the
command’s undo state in sync with the latest apply.

In `@src/LightManager.cpp`:
- Around line 336-337: The Sentry breadcrumbs in LightManager use non-standard
categories, so update the addBreadcrumb calls in LightManager::createKeyLight,
LightManager::duplicateLight, and LightManager::renameLight to use one of the
approved categories instead of scene.light.create/duplicate/rename, while
keeping the existing message text intact.
- Around line 301-314: deleteAllUserLights() and clearAllLights() currently
duplicate the same loop-and-delete logic in LightManager, so consolidate them to
a single implementation to prevent drift; choose one method as the source of
truth and have the other delegate to it, keeping the deleteLight(name) behavior
unchanged and using the existing LightManager methods/fields (m_lights,
deleteLight) to preserve semantics.
- Around line 200-214: The stacking offset in LightManager::createLightInternal
is using m_lights.size(), which can repeat after deletions and cause new lights
to spawn on top of existing ones. Replace this size-based index with a monotonic
counter that only increases for each created light, and use that counter to
compute stackedOffset so each newly created light gets a unique position
regardless of removals.

In `@src/LightPropertiesController.cpp`:
- Around line 136-137: The breadcrumb category used in
LightPropertiesController::updateLightProperty is not one of the established
Sentry categories. Update the SentryReporter::addBreadcrumb call to use a valid
category from the approved set (ui.action, ai.tool_call, file.import, or
file.export) while keeping the existing message from
lightPropertyClassLabel(propertyClass). Ensure the breadcrumb still clearly
represents the light edit action but matches the coding guidelines.

In `@src/LightRigLibrary.cpp`:
- Around line 399-411: The breadcrumb in the rig-application path is using a
non-standard category, so update the SentryReporter::addBreadcrumb call in
LightRigLibrary::applyRig to use one of the established categories instead of
"scene.light.apply_rig". Keep the message detail (rigId) but route it through
the appropriate category for a user-facing or significant operation, consistent
with the breadcrumb guidelines.

In `@src/LightsController.cpp`:
- Around line 147-148: The Sentry breadcrumb categories in LightsController use
non-standard values for light create/delete actions. Update the
SentryReporter::addBreadcrumb calls in the light create and light delete paths
to use one of the established categories (such as ui.action) while keeping the
existing action-specific message, and ensure both the create and delete
breadcrumb sites are made consistent.
- Around line 184-216: The multi-light duplication flow in
duplicateSelectedLights is reselecting clones through selectLightHandle, which
uses single-selection behavior and clears earlier picks, so only the last
duplicated light remains selected. After clearing the current selection, update
this path to select all valid clone names in a multi-select way instead of
calling selectLightHandle in a loop, using the existing SelectionSet selection
API or an equivalent multi-selection helper so every entry in cloneNames stays
selected.

In `@src/LightsController.h`:
- Line 31: `LightsController::renameLight` currently hides rename rejection by
returning void, which causes callers like
`PropertiesPanelController::renameSceneTreeLight` to refresh the UI even when
the manager rejects the change. Update `renameLight` to return a success flag
and propagate the manager result from the rename path, then have
`renameSceneTreeLight` check that result before emitting `selectionChanged()`.
Keep the change aligned with the existing `LightsController` and
`PropertiesPanelController` methods so the UI only updates on successful
renames.

In `@src/LightVisualizer_test.cpp`:
- Around line 42-43: The Ogre fixture setup in LightVisualizer_test is missing
the mesh-file prerequisite check, so add an ASSERT_TRUE(canLoadMeshFiles())
alongside the existing ASSERT_TRUE(tryInitOgre()) in the fixture initialization
path before createStandardOgreMaterials() runs. Use the fixture setup code in
the test class to fail loudly in CI when mesh loading is unavailable, matching
the existing loud prerequisite pattern.

In `@src/LightVisualizer.cpp`:
- Around line 313-315: The breadcrumb in LightVisualizer’s user-facing View-menu
toggle uses a custom category instead of an established Sentry category. Update
the SentryReporter::addBreadcrumb calls in the relevant gizmo visibility
handlers to use the standard ui.action category while keeping the existing
show/hide message text, and apply the same change to the other matching
breadcrumb call in this file so both actions are consistent.
- Around line 419-422: The overlay insertion order in LightVisualizer is wrong:
updateOverlayVisibility(handle.name) runs before the new overlay is present in
mOverlays, so it can no-op and leave newly created unselected gizmos visible
when “selected gizmos only” is enabled. In the LightVisualizer update path
around rebuildGizmoGeometry and updateOverlayVisibility, insert the new entry
into mOverlays first, then call updateOverlayVisibility(handle.name) so the
visibility state is applied to the freshly created overlay.
- Around line 172-204: The cached icon material is not being cleared before
recreating it, so repeated calls to uploadIconTexture() can fail when
MaterialManager::create() sees an existing LightVisualizer/Icon/<name>/Mat.
Update uploadIconTexture() to mirror the existing texture cleanup by removing
the old material resource first, using the same texName-derived material name
before creating the new Ogre::MaterialPtr.

In `@src/mainwindow.cpp`:
- Around line 1287-1340: The new Add Light toolbar/menu actions in
MainWindow::setupObjectsToolbar are missing Sentry breadcrumbs, so update each
QAction trigger lambda in the light menu setup to call
SentryReporter::addBreadcrumb with the established ui.action category before
invoking LightsController::instance() or
SceneLightingController::instance().applyRig; include the light type or preset
rig name in the breadcrumb message, and apply the same pattern used by the
sibling actions later in this function so all user-facing lighting actions are
covered.
- Around line 2428-2435: m_lightVisualizer is being torn down too late, after
Manager::kill() can invalidate the Ogre scene manager it depends on. Move its
deletion into the early-teardown path in MainWindow, alongside m_meshInfoOverlay
and m_normalVisualizer, so LightVisualizer::~LightVisualizer() runs while
mSceneMgr is still valid. Use the m_lightVisualizer member and the existing
teardown block in MainWindow to place the cleanup in the correct order.

In `@src/SceneLightingController.cpp`:
- Around line 118-145: SceneLightingController::applyRig performs a major
user-facing scene mutation but does not record a Sentry breadcrumb. Add a
SentryReporter::addBreadcrumb call at the start of applyRig, using the
established "ui.action" category and a message describing the rig application
action, similar to the toolbar action breadcrumbs in mainwindow.cpp. Keep it
before the early returns so both successful and rejected attempts are captured.

In `@src/TransformOperator.cpp`:
- Around line 539-572: The removeSelected() flow is incorrectly short-circuiting
mixed selections by calling LightsController::instance()->deleteSelectedLights()
whenever all selected nodes are lights, which clears the entire SelectionSet and
drops any selected entities or sub-entities. Update the logic around
SelectionSet::getSingleton(), hasNodes(), and deleteSelectedLights() so this
branch only runs for node-only/light-only selections, or process the remaining
selection types before returning. Keep the fallback destroySceneNode loop and
selection cleanup path for mixed selections so nothing is lost.

---

Outside diff comments:
In `@src/commands/LightCommands_test.cpp`:
- Around line 1-90: The test suite in LightCommandsOgreTest only covers
create/delete, duplicate, and rename flows, so add coverage for the new
ApplyLightRigCommand and SetSceneAmbientCommand. Extend this test file with
Google Test cases that exercise undo→redo→undo behavior for
ApplyLightRigCommand, specifically verifying redo after undo refreshes state and
does not reuse stale m_addedLights or m_rigGroupNodeName. Also add a basic
undo/redo test for SetSceneAmbientCommand, using the existing LightManager,
UndoManager, and LightsController helpers to validate the command lifecycle.

In `@src/Manager.cpp`:
- Around line 128-141: Move the LightManager hookup out of Manager::Manager so
it runs only after Manager::m_pSingleton has been assigned. The current call to
LightManager::getSingleton()->tryConnectToManager() happens too early in the
constructor, causing the connection logic to return before sceneNodeDestroyed is
wired; remove that call from Manager::Manager and perform it after new
Manager(parent) completes or in a later initialization method that runs once the
singleton is ready.

---

Duplicate comments:
In `@src/PropertiesPanelController.cpp`:
- Around line 338-342: The rename flow in
PropertiesPanelController::renameSceneTreeLight currently assumes
LightsController::renameLight always succeeds, so selectionChanged() is emitted
even when the rename is rejected. Update LightsController::renameLight in
LightsController.h/.cpp to return a success/failure result for invalid or
duplicate names, then adjust PropertiesPanelController::renameSceneTreeLight to
emit selectionChanged() only on success and handle the failure path
appropriately.

---

Nitpick comments:
In `@src/LightRigLibrary_test.cpp`:
- Around line 1-111: Add a Google Test that exercises the error path in
LightRigLibrary::apply() for an unknown rig id, since the current suite only
covers valid rigs. Use the existing LightRigLibraryOgreTest fixture and call
LightRigLibrary::apply() with a non-existent id, then assert result.ok is false
and result.error is populated to cover the “Unknown light rig” behavior.

In `@src/LightRigLibrary.cpp`:
- Around line 368-407: The apply() path in LightRigLibrary::apply leaves behind
a tagged rigGroup node when no lights are successfully created, and it silently
ignores per-light creation failures. Update the loop around
createLightUnderParent to record failed light specs (or at least surface a
failure when none succeed), and if result.ok remains false after the loop,
destroy the rigGroup node before returning. Use the existing rigGroup,
result.addedLights, and result.error handling in apply() to keep the cleanup and
failure reporting localized.

In `@src/LightsController.cpp`:
- Around line 280-294: The rename flow in LightsController::renameLight
currently returns silently when LightManager::findLight or
LightManager::renameLight fails, so the UI cannot explain why the rename did not
apply. Update LightsController::renameLight to report failure back to QML,
either by emitting a renameFailed(QString reason) signal or by returning a
status that qml/SceneTreeNode.qml can inspect, and make sure the failure paths
for missing oldName and renameLight rejection provide a user-visible reason
while preserving the existing success path that pushes RenameLightCommand and
calls selectLightHandle.

In `@src/PropertiesPanel_qml_test.cpp`:
- Around line 9-12: The path lookup in propertiesPanelQmlPath() is using
QDir::currentPath(), which makes the test depend on the process working
directory and breaks in different build layouts. Update this helper to resolve
PropertiesPanel.qml from a stable test/data location instead of the current
directory, for example by using a compile-time configured source/test root or
QFINDTESTDATA so the PropertiesPanel_qml_test.cpp lookup works across Windows,
Linux, and macOS. Keep the change localized to propertiesPanelQmlPath().
🪄 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: d5e98f47-d5c5-4134-a56f-e3b295f1c6e5

📥 Commits

Reviewing files that changed from the base of the PR and between cafded3 and 3407eb0.

📒 Files selected for processing (35)
  • qml/PropertiesPanel.qml
  • qml/SceneTreeNode.qml
  • src/AppSettingsKeys.h
  • src/CMakeLists.txt
  • src/GlobalDefinitions.h
  • src/LightManager.cpp
  • src/LightManager.h
  • src/LightManager_test.cpp
  • src/LightPropertiesController.cpp
  • src/LightPropertiesController.h
  • src/LightPropertiesController_test.cpp
  • src/LightRigLibrary.cpp
  • src/LightRigLibrary.h
  • src/LightRigLibrary_test.cpp
  • src/LightVisualizer.cpp
  • src/LightVisualizer.h
  • src/LightVisualizer_test.cpp
  • src/LightsController.cpp
  • src/LightsController.h
  • src/Manager.cpp
  • src/PropertiesPanelController.cpp
  • src/PropertiesPanelController.h
  • src/PropertiesPanel_qml_test.cpp
  • src/SceneLightingController.cpp
  • src/SceneLightingController.h
  • src/SceneTreeModel.cpp
  • src/SceneTreeModel.h
  • src/TransformOperator.cpp
  • src/commands/LightCommands.cpp
  • src/commands/LightCommands.h
  • src/commands/LightCommands_test.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
  • src/mainwindow_test.cpp
  • ui_files/mainwindow.ui

Comment thread qml/PropertiesPanel.qml
Comment thread qml/PropertiesPanel.qml
Comment thread qml/SceneTreeNode.qml
Comment thread src/commands/LightCommands.cpp
Comment thread src/LightManager.cpp
Comment thread src/LightVisualizer.cpp
Comment thread src/mainwindow.cpp
Comment thread src/mainwindow.cpp
Comment on lines +118 to +145
void SceneLightingController::applyRig(const QString& rigId, bool replaceExisting)
{
auto* lights = LightManager::getSingleton();
if (!lights)
return;

QWidget* parent = Manager::getSingletonPtr() ? Manager::getSingleton()->getMainWindow() : nullptr;
if (replaceExisting && !lights->lights().isEmpty()
&& !confirmReplaceExisting(parent))
{
return;
}

const LightRigApplyResult result = LightRigLibrary::apply(rigId, replaceExisting);
if (!result.ok)
{
if (!result.error.isEmpty())
{
QMessageBox::warning(parent,
QObject::tr("Light rig"),
result.error);
}
return;
}

UndoManager::getSingleton()->push(new ApplyLightRigCommand(result));
maybeLoadSuggestedHdri(result.suggestedHdri);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Missing Sentry breadcrumb for a significant user action.

applyRig mutates the scene (destroys rig groups/lights, potentially deletes all user lights, pushes an undo command) but never calls SentryReporter::addBreadcrumb. Every comparable user-triggered operation elsewhere in this cohort (toolbar Extrude/Bevel/Delete/etc. in mainwindow.cpp) adds a "ui.action" breadcrumb.

📝 Proposed fix
 void SceneLightingController::applyRig(const QString& rigId, bool replaceExisting)
 {
     auto* lights = LightManager::getSingleton();
     if (!lights)
         return;
 
+    SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
+                                  QStringLiteral("Apply light rig: %1").arg(rigId));
+
     QWidget* parent = Manager::getSingletonPtr() ? Manager::getSingleton()->getMainWindow() : nullptr;

As per coding guidelines, "All user-facing actions and significant operations must add a Sentry breadcrumb via SentryReporter::addBreadcrumb(category, message) using the established categories (ui.action, ai.tool_call, file.import, file.export)."

📝 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
void SceneLightingController::applyRig(const QString& rigId, bool replaceExisting)
{
auto* lights = LightManager::getSingleton();
if (!lights)
return;
QWidget* parent = Manager::getSingletonPtr() ? Manager::getSingleton()->getMainWindow() : nullptr;
if (replaceExisting && !lights->lights().isEmpty()
&& !confirmReplaceExisting(parent))
{
return;
}
const LightRigApplyResult result = LightRigLibrary::apply(rigId, replaceExisting);
if (!result.ok)
{
if (!result.error.isEmpty())
{
QMessageBox::warning(parent,
QObject::tr("Light rig"),
result.error);
}
return;
}
UndoManager::getSingleton()->push(new ApplyLightRigCommand(result));
maybeLoadSuggestedHdri(result.suggestedHdri);
}
void SceneLightingController::applyRig(const QString& rigId, bool replaceExisting)
{
auto* lights = LightManager::getSingleton();
if (!lights)
return;
SentryReporter::addBreadcrumb(QStringLiteral("ui.action"),
QStringLiteral("Apply light rig: %1").arg(rigId));
QWidget* parent = Manager::getSingletonPtr() ? Manager::getSingleton()->getMainWindow() : nullptr;
if (replaceExisting && !lights->lights().isEmpty()
&& !confirmReplaceExisting(parent))
{
return;
}
const LightRigApplyResult result = LightRigLibrary::apply(rigId, replaceExisting);
if (!result.ok)
{
if (!result.error.isEmpty())
{
QMessageBox::warning(parent,
QObject::tr("Light rig"),
result.error);
}
return;
}
UndoManager::getSingleton()->push(new ApplyLightRigCommand(result));
maybeLoadSuggestedHdri(result.suggestedHdri);
}
🤖 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/SceneLightingController.cpp` around lines 118 - 145,
SceneLightingController::applyRig performs a major user-facing scene mutation
but does not record a Sentry breadcrumb. Add a SentryReporter::addBreadcrumb
call at the start of applyRig, using the established "ui.action" category and a
message describing the rig application action, similar to the toolbar action
breadcrumbs in mainwindow.cpp. Keep it before the early returns so both
successful and rejected attempts are captured.

Source: Coding guidelines

Comment thread src/TransformOperator.cpp
Comment on lines +539 to +572
SentryReporter::addBreadcrumb("ui.action", "Remove selected objects");
SelectionSet* pCurrentSelection = SelectionSet::getSingleton();
if (pCurrentSelection->isEmpty())
return;

if (pCurrentSelection->hasNodes())
{
bool allLights = true;
for (Ogre::SceneNode* node : pCurrentSelection->getNodesSelectionList())
{
foreach(Ogre::SceneNode* node,SelectionSet::getSingleton()->getNodesSelectionList())
{
Manager::getSingleton()->destroySceneNode(node);
}
pCurrentSelection->clearList();
if (!LightManager::sceneNodeIsUserLight(node))
{
allLights = false;
break;
}
}
if (allLights)
{
LightsController::instance()->deleteSelectedLights();
return;
}
}

// Clear undo stack — destroyed nodes invalidate any stored commands
UndoManager::getSingleton()->clear();
if (!pCurrentSelection->isEmpty())
{
foreach (Ogre::SceneNode* node, SelectionSet::getSingleton()->getNodesSelectionList())
{
Manager::getSingleton()->destroySceneNode(node);
}
pCurrentSelection->clearList();

// Clear undo stack — destroyed nodes invalidate any stored commands
UndoManager::getSingleton()->clear();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant code and nearby selection APIs.
for f in src/TransformOperator.cpp src/SelectionSet.* src/LightsController.* src/Manager.*; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
  fi
done

echo "===== TransformOperator.cpp around removeSelected ====="
sed -n '520,590p' src/TransformOperator.cpp

echo "===== SelectionSet declarations/definitions ====="
grep -nE 'isEmpty|hasNodes|hasSubEntities|hasEntities|getNodesSelectionList|getSubEntitiesSelectionList|getEntitiesSelectionList|clearList|remove' -n src/SelectionSet.* || true

echo "===== LightsController relevant parts ====="
grep -nE 'deleteSelectedLights|isLightNode|sceneNodeIsUserLight|getSceneNode' -n src/LightsController.* src/TransformOperator.cpp src/Manager.* || true

Repository: fernandotonon/QtMeshEditor

Length of output: 9003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant implementation slices if the above search shows them.
sed -n '1,260p' src/SelectionSet.h 2>/dev/null || true
sed -n '1,320p' src/SelectionSet.cpp 2>/dev/null || true
sed -n '1,260p' src/LightsController.h 2>/dev/null || true
sed -n '1,360p' src/LightsController.cpp 2>/dev/null || true

Repository: fernandotonon/QtMeshEditor

Length of output: 21767


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether delete/remove handling exists for entities or sub-entities elsewhere.
rg -n "hasEntities\\(|hasSubEntities\\(|getEntitiesSelectionList\\(|getSubEntitiesSelectionList\\(|deleteSelected.*Entity|destroyEntity|destroySubEntity|removeSelected" src | sed -n '1,220p'

Repository: fernandotonon/QtMeshEditor

Length of output: 12552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect tests around removeSelected to understand intended behavior.
sed -n '640,730p' src/TransformOperator_test.cpp

echo "===== nearby removeSelected references in commands ====="
sed -n '140,220p' src/commands/TransformCommands.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 6524


removeSelected() should not short-circuit mixed selections
deleteSelectedLights() clears the whole SelectionSet, so the light-only branch drops any selected entities/sub-entities without deleting them. Restrict this path to node-only selections, or handle the remaining selection types before returning.

🤖 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/TransformOperator.cpp` around lines 539 - 572, The removeSelected() flow
is incorrectly short-circuiting mixed selections by calling
LightsController::instance()->deleteSelectedLights() whenever all selected nodes
are lights, which clears the entire SelectionSet and drops any selected entities
or sub-entities. Update the logic around SelectionSet::getSingleton(),
hasNodes(), and deleteSelectedLights() so this branch only runs for
node-only/light-only selections, or process the remaining selection types before
returning. Keep the fallback destroySceneNode loop and selection cleanup path
for mixed selections so nothing is lost.

fernandotonon and others added 2 commits July 3, 2026 22:05
Register light epic sources in qtmesh_test_common, pair outdoor overcast with overcast_outdoor.hdr, and harden rig undo/redo, overlay visibility, multi-select duplicate, and teardown order.

Co-authored-by: Cursor <cursoragent@cursor.com>
Capture the active environment before applying a preset rig so undo restores the previous skybox and redo reloads the rig's suggested HDRI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Addressed review feedback in b29fe3e + f5b3822:

  • CI: registered all light epic sources in tests/CMakeLists.txt (qtmesh_test_common linker fix).
  • Outdoor Overcast skybox: rig now suggests overcast_outdoor.hdr (test in LightRigLibrary_test).
  • P1 mergeWith: EditLightPropertyCommand only merges when the affected light name set matches.
  • ApplyLightRigCommand redo: replays stored snapshots via destroyAllRigGroups + createRigGroupForRig instead of re-calling LightRigLibrary::apply().
  • HDRI undo/redo (P2): ApplyLightRigCommand captures/restores the previous environment; redo reloads the rig's suggested HDRI.
  • LightVisualizer: insert overlay before visibility update (selected-only gizmo fix).
  • Multi-select duplicate: uses SelectionSet::append instead of repeated selectOne.
  • LightManager: monotonic stack offset counter (no collision after deletes).
  • SceneTreeNode: right-click no longer changes selection on non-light rows.
  • MainWindow teardown: destroy LightVisualizer before Manager::kill().
  • Sentry: light actions use ui.action category per project guidelines.

Remaining known limitation (P2): undoing a rig apply restores removed rig lights as root-level nodes rather than re-parenting into the old rig group — follow-up if we want full rig-group snapshot restore.

Remove existing icon materials before recreate so a second MainWindow in tests does not crash, and resolve PropertiesPanel.qml via QTMESH_UT_SOURCE_ROOT in CI.

Co-authored-by: Cursor <cursoragent@cursor.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: 1

🤖 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/commands/LightCommands.cpp`:
- Around line 290-295: The recreated rig and light names are not being refreshed
after redo, so undo can miss the new objects if name collisions caused suffixes
to be appended. In `LightCommands::redo()`, capture the actual names returned by
`LightRigLibrary::createRigGroupForRig()` and `restoreSnapshotUnderParent()` and
update `m_rigGroupNodeName` and each `m_addedLights[].name` accordingly so later
`undo()` targets the recreated rig/light nodes correctly.
🪄 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: da1208a7-6ae9-4967-998f-1a0df86c2491

📥 Commits

Reviewing files that changed from the base of the PR and between 3407eb0 and 7933c00.

📒 Files selected for processing (14)
  • qml/SceneTreeNode.qml
  • src/LightManager.cpp
  • src/LightManager.h
  • src/LightPropertiesController.cpp
  • src/LightRigLibrary.cpp
  • src/LightRigLibrary.h
  • src/LightVisualizer.cpp
  • src/LightsController.cpp
  • src/PropertiesPanel_qml_test.cpp
  • src/SceneLightingController.cpp
  • src/commands/LightCommands.cpp
  • src/commands/LightCommands.h
  • src/mainwindow.cpp
  • tests/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/PropertiesPanel_qml_test.cpp
  • qml/SceneTreeNode.qml
  • src/commands/LightCommands.h
  • src/LightManager.h
  • src/LightVisualizer.cpp
  • src/SceneLightingController.cpp
  • src/LightRigLibrary.cpp
  • src/LightPropertiesController.cpp
  • src/mainwindow.cpp
  • src/LightsController.cpp
  • src/LightManager.cpp

Comment on lines +290 to +295
Ogre::SceneNode* rigGroup = LightRigLibrary::createRigGroupForRig(m_rigId);
if (!rigGroup)
return;

for (const LightSnapshot& snapshot : m_addedLights)
lights->restoreSnapshotUnderParent(rigGroup, snapshot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect rig-group node and light naming to confirm determinism across delete+recreate.
fd -a 'LightManager.cpp' src | xargs -r -I{} ast-grep run --pattern 'Ogre::SceneNode* $_::createRigGroupNode($$$) { $$$ }' --lang cpp {}
rg -nP -C3 '\bcreateRigGroupNode\b|\brestoreSnapshotUnderParent\b|\brestoreSnapshot\b' src/LightManager.cpp

Repository: fernandotonon/QtMeshEditor

Length of output: 1496


🏁 Script executed:

#!/bin/bash
sed -n '272,430p' src/LightManager.cpp | cat -n

Repository: fernandotonon/QtMeshEditor

Length of output: 6051


🏁 Script executed:

#!/bin/bash
sed -n '1,360p' src/commands/LightCommands.cpp | cat -n

Repository: fernandotonon/QtMeshEditor

Length of output: 11841


🏁 Script executed:

#!/bin/bash
sed -n '1,260p' src/LightRigLibrary.cpp | cat -n

Repository: fernandotonon/QtMeshEditor

Length of output: 10338


🏁 Script executed:

#!/bin/bash
sed -n '260,420p' src/LightRigLibrary.cpp | cat -n

Repository: fernandotonon/QtMeshEditor

Length of output: 6408


Refresh the recreated rig/light names in redo() src/commands/LightCommands.cpp:290-295createRigGroupForRig() and restoreSnapshotUnderParent() can append suffixes on name collisions, but this command keeps the original m_rigGroupNodeName and m_addedLights[].name; capture the returned names so a later undo() still targets the recreated objects.

🤖 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/commands/LightCommands.cpp` around lines 290 - 295, The recreated rig and
light names are not being refreshed after redo, so undo can miss the new objects
if name collisions caused suffixes to be appended. In `LightCommands::redo()`,
capture the actual names returned by `LightRigLibrary::createRigGroupForRig()`
and `restoreSnapshotUnderParent()` and update `m_rigGroupNodeName` and each
`m_addedLights[].name` accordingly so later `undo()` targets the recreated
rig/light nodes correctly.

fernandotonon and others added 2 commits July 4, 2026 01:28
Fix LightVisualizer teardown exceptions, restore rig group parentage on undo, add keyboard/a11y for lighting controls, unify clearAllLights, and add missing ui.action breadcrumbs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Follow-up in db21018 (review + Sonar fixes):

  • SonarCloud reliability (S1048): LightVisualizer destructor/overlay teardown wrapped in try/catch so Ogre shutdown cannot throw through the destructor.
  • P2 rig membership undo: ApplyLightRigCommand now stores removedRigGroups and restores lights back under their rig group nodes on undo (test: ApplyLightRigUndoRestoresRigGroupParent).
  • P2 HDRI undo: already in f5b3822 — command captures/restores previous environment.
  • Keyboard/a11y: diffuse/specular color swatches + “Apply preset rig” button are focusable with Accessible roles and Space/Enter activation.
  • SceneTreeNode: right-click on non-light rows no longer changes selection (from b29fe3e).
  • Sentry: Add Light menu/toolbar actions + SceneLightingController::applyRig now use ui.action.
  • Maintainability: clearAllLights() delegates to deleteAllUserLights().
  • Tests: PropertiesPanel_qml_test source path + LightVisualizer_test mesh-file prerequisite.

Prior CI rerun: unit-tests-linux passed (32m). Watching new run for Sonar quality gate.

canLoadMeshFiles() was checked before tryInitOgre(), so the suite always
GTEST_SKIP'd under CI's no-skipped-tests policy.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

CI fix in d62720e: LightVisualizerOgreTest was calling canLoadMeshFiles() before tryInitOgre(), so all four tests were GTEST_SKIP'd — CI treats skips as failure. Reordered to match other Ogre fixtures (TranslationGizmo_test, etc.).

@sonarqubecloud

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit 627287d into master Jul 4, 2026
21 checks passed
@fernandotonon fernandotonon deleted the feat/lights-epic-482-487 branch July 4, 2026 15:14
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.

Lights: Slice A — LightManager foundation + replace hard-coded light

1 participant