Skip to content

UV: Slice E — Projection unwrap modes (#463) - #776

Merged
fernandotonon merged 2 commits into
masterfrom
feature/uv-projections-463
Jun 29, 2026
Merged

UV: Slice E — Projection unwrap modes (#463)#776
fernandotonon merged 2 commits into
masterfrom
feature/uv-projections-463

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add UvProject with view, box, cylinder, sphere, and reset-to-0–1 box projection modes scoped to the active UV selection (full sub-mesh when nothing is selected).
  • Wire projection tools into Material Mode UV Edit and the detached UV editor under labeled Seams / Projection groups; each operation pushes a single UVEditCommand for undo and records mesh.uv.project Sentry breadcrumbs.
  • View projection uses the last active 3D viewport camera (transform gizmo / view cube tracking) so UV panel clicks do not require viewport focus.

Test plan

  • ./build_local/bin/UnitTests --gtest_filter="UvProject*" (box, cylinder, view guard, reset)
  • Material Mode → UV Edit: Projection buttons (View, Box, Cyl, Sph, Reset) on humanoid + prop mesh
  • View matches 3D viewport orientation after clicking viewport once
  • Ctrl+Z undoes each projection
  • Partial UV selection scopes projection; status shows (selection)

Closes #463

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added new UV projection tools in the editor, including view-based, box, cylinder, sphere, and reset options.
    • Improved the UV editing panel with clearer grouping for seam and projection controls.
    • Projection tools now appear only when a mesh is available, keeping the interface cleaner.
  • Tests

    • Added coverage for the new UV projection modes to verify results and error handling.

Expose view/box/cylinder/sphere/reset projections in the UV editor with undo, viewport camera integration, and unit tests for the core projector.

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

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 18 minutes and 33 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2eba3be1-9251-4c4c-a518-621d0a63f978

📥 Commits

Reviewing files that changed from the base of the PR and between 413184e and 9141a3e.

📒 Files selected for processing (5)
  • src/UVEditorController.cpp
  • src/UvProject.cpp
  • src/UvProject_test.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
📝 Walkthrough

Walkthrough

Adds UV geometric projection unwrap modes (Box, Cylinder, Sphere, View, ResetBox) via a new UvProject module. UVEditorController gains QML-invokable entry points that build triangle selections, query viewport camera matrices from MainWindow, run projection, and commit results with undo/breadcrumb support. Both QML UV panels expose the new projection buttons.

UV Projection Unwrap Modes

Layer / File(s) Summary
UvProject public API
src/UvProject.h, src/CMakeLists.txt, tests/CMakeLists.txt
Defines Mode enum, VertChange, Selection, Options, Report structs, and project/modeToString static declarations; registers the module in both build systems.
UvProject projection math
src/UvProject.cpp
Implements per-mode UV generation (box, cylinder, sphere, view NDC, resetBox), normalization, affected-vertex collection, and UV write-back with Report population.
MainWindow viewport camera snapshot
src/mainwindow.h, src/mainwindow.cpp
Adds ViewportCameraSnapshot struct and queryViewportCamera() selecting from focused viewport, TransformOperator fallback, or first viewport.
UVEditorController projection integration
src/UVEditorController.h, src/UVEditorController.cpp
Adds QML-invokable projectUvFromView/Box/Cylinder/Sphere/resetUvBox, private helpers buildProjectionSelection, applyProjectionChanges (with undo push and breadcrumb), and runUvProjection orchestrating the full pipeline.
QML projection UI
qml/PropertiesPanel.qml, qml/UVEditorPanel.qml
Adds "Seams" label and "Projection" button row (View/Box/Cyl/Sph/Reset) in both panels, calling controller methods and refreshing triangle cache.
Unit tests
src/UvProject_test.cpp
GoogleTest coverage for Box, Cylinder, View (missing matrices), and ResetBox modes with mesh fixtures and UV range validators.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • fernandotonon/QtMeshEditor#759: Adds the foundational UVEditorController and UVEditorPanel.qml viewer that this PR extends with projection methods and UI buttons.

Poem

🐇 Hippity-hop, the UVs unfold,
Box and cylinder, sphere and view bold,
Each face projected with mathly delight,
Normals and matrices set everything right,
Undo the seams if they don't look just right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers view, box, cylinder, sphere, and reset, but it does not implement Smart UV project or its required tests from #463. Add Smart UV project using HalfEdgeMesh angle-threshold clustering, and include fixture tests for smart projection plus the missing acceptance coverage.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly matches the main change: adding UV projection unwrap modes.
Description check ✅ Passed It provides a clear summary and test plan that align with the template's intent.
Out of Scope Changes check ✅ Passed The UI, controller, mesh projection, mainwindow, CMake, and test changes all support the UV projection feature and stay within scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/uv-projections-463

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: 413184e50a

ℹ️ 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/UvProject.cpp
Comment on lines +265 to +266
verts[static_cast<size_t>(vi)].uv = change.newUv;
verts[static_cast<size_t>(vi)].hasUV = 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.

P2 Badge Propagate projected UVs across shared vertex buffers

When projecting only a selected sub-entity/material on meshes that use Ogre shared vertex data, this writes the new UV only into that submesh's EditableMesh copy. EditableMesh::commitUvsToEntity() later merges every submesh using the shared buffer, so an unprojected later submesh can overwrite these UVs; existing UV edits avoid this by routing writes through applyWorkingMeshUv(), which fans the value out to all shared users. Please propagate each projected change to the other shared submeshes before committing, otherwise Projection buttons can appear to do nothing or commit stale UVs for shared-vertex meshes.

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

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

90-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a Sphere projection test.

This file covers Box/Cylinder/View/Reset only, so the new Sphere path can regress without CI noticing. A per-axis assertion would also catch the current axis no-op in projectSphere(). As per coding guidelines, src/**/*_test.cpp: Add Google Test unit tests for new functionality.

🤖 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/UvProject_test.cpp` around lines 90 - 150, Add a Google Test case in
UvProjectTest to cover the Sphere projection path in UvProject::project, since
the current suite only exercises Box, Cylinder, View, and ResetBox. Use
UvProject::Options with mode set to UvProject::Mode::Sphere and verify the
report is applied and the resulting UVs are in range. Also include a per-axis
assertion on the projected UV extents so the test fails if projectSphere()
ignores opts.axis.

Source: Coding guidelines

🤖 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 2673-2678: The UV projection buttons are still hardcoding default
axis/scale values in the PropertiesPanel and matching detached editor UI, so
users cannot choose projection settings. Update the action handlers in
PropertiesPanel and the mirrored controls in UVEditorPanel to pass user-selected
axis and scale into UVEditorController.projectUvBox, projectUvCylinder, and
projectUvSphere instead of fixed constants. Wire the UI to expose X/Y/Z axis
selection and a configurable projection scale, and keep the button
labels/handlers aligned with those new parameters.

In `@src/mainwindow.cpp`:
- Around line 4409-4413: The fallback in mainwindow’s viewport selection only
checks mDockWidgetList.first(), which can miss a valid 3D viewport if the first
EditorViewport is stale or widget-less. Update the spaceCam lookup logic to
iterate through mDockWidgetList and pick the first usable EditorViewport with a
non-null Ogre widget and SpaceCamera, reusing the same null-safety checks
already used in the focus search path.

In `@src/mainwindow.h`:
- Around line 55-58: Make the Ogre::Matrix4 dependency explicit in mainwindow.h:
ViewportCameraSnapshot stores Ogre::Matrix4 by value, so add the direct
OgreMatrix4.h include in this header rather than relying on OgreFrameListener.h
transitively providing the type. Keep the struct definition unchanged and ensure
the header can compile independently with the matrix definition available.

In `@src/UVEditorController.cpp`:
- Around line 2298-2305: The success path in UVEditorController::apply
projection flow is updating m_statusText after the change notification already
goes out, leaving the QML status line stale; set the success status before
calling applyProjectionChanges(), or immediately re-emit the status update after
assigning m_statusText so the new message is published. Keep the fix within the
branch that uses modeToString, undoDescription, and the scoped selection check.

In `@src/UvProject_test.cpp`:
- Line 46: Replace the non-portable use of M_PI in the angle calculation with a
portable π constant in the sphere-related test setup, using the existing test
helper or a local constexpr so the build works on MSVC. Also expand
UvProject_test to add Sphere-mode coverage by invoking the Sphere path in the
UvProject tests, so projectSphere() is exercised and the axis-handling behavior
is validated.

In `@src/UvProject.cpp`:
- Around line 333-344: The UvProject::projectView path is being renormalized
again after it already returns viewport UVs, which changes the camera framing.
Update the post-processing in UvProject.cpp so normalizeUvMap(projected) is
skipped for Mode::View and only applied for the other projection modes
(Cylinder, Sphere, ResetBox), keeping projectView() output unchanged.
- Around line 161-173: The Sphere projection helper currently ignores the
selected axis, so all UVs are computed the same regardless of X/Y/Z. Update
projectSphere in UvProject.cpp to use the axis parameter when deriving the
spherical coordinates, and make sure the UV mapping differs per axis while
preserving the existing bounds-center based projection behavior.
- Around line 213-218: The box and cylinder UV mapping paths in UvProject
currently multiply by scale and then call normalizeUvMap on the full output,
which makes boxScale ineffective. Update the UV generation logic so scale
actually affects the final coordinates in the box/cylinder projection code (and
the related projectPositionOnBoxPlane / cylinder path), or remove the final
renormalization step if it cancels the scaling; keep the output in the intended
range without undoing boxScale.

In `@src/UvProject.h`:
- Around line 15-21: The public UvProject::Mode enum is missing the
smart/angle-threshold unwrap option, so add the required Smart UV projection
mode alongside the existing View, Box, Cylinder, Sphere, and ResetBox values.
Update any related UvProject API handling to recognize and route this new mode
wherever Mode is used so the workflow can be exposed and tested end to end.

---

Nitpick comments:
In `@src/UvProject_test.cpp`:
- Around line 90-150: Add a Google Test case in UvProjectTest to cover the
Sphere projection path in UvProject::project, since the current suite only
exercises Box, Cylinder, View, and ResetBox. Use UvProject::Options with mode
set to UvProject::Mode::Sphere and verify the report is applied and the
resulting UVs are in range. Also include a per-axis assertion on the projected
UV extents so the test fails if projectSphere() ignores opts.axis.
🪄 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: 84178be8-430a-4f87-9af4-74c4dc41ddce

📥 Commits

Reviewing files that changed from the base of the PR and between 9139464 and 413184e.

📒 Files selected for processing (11)
  • qml/PropertiesPanel.qml
  • qml/UVEditorPanel.qml
  • src/CMakeLists.txt
  • src/UVEditorController.cpp
  • src/UVEditorController.h
  • src/UvProject.cpp
  • src/UvProject.h
  • src/UvProject_test.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
  • tests/CMakeLists.txt

Comment thread qml/PropertiesPanel.qml
Comment on lines +2673 to +2678
model: [
{ label: "View", fn: function() { UVEditorController.projectUvFromView() } },
{ label: "Box", fn: function() { UVEditorController.projectUvBox(1.0) } },
{ label: "Cyl", fn: function() { UVEditorController.projectUvCylinder(1, 1.0) } },
{ label: "Sph", fn: function() { UVEditorController.projectUvSphere(1) } },
{ label: "Reset", fn: function() { UVEditorController.resetUvBox() } }

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 | 🟠 Major | 🏗️ Heavy lift

Expose projection axis/scale instead of hardcoding the defaults.

These buttons only surface projectUvBox(1.0), projectUvCylinder(1, 1.0), and projectUvSphere(1) here, and the detached editor mirrors the same constants in qml/UVEditorPanel.qml. That means users still cannot choose the requested X/Y/Z axis or configurable projection scale from the UI.

🤖 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 `@qml/PropertiesPanel.qml` around lines 2673 - 2678, The UV projection buttons
are still hardcoding default axis/scale values in the PropertiesPanel and
matching detached editor UI, so users cannot choose projection settings. Update
the action handlers in PropertiesPanel and the mirrored controls in
UVEditorPanel to pass user-selected axis and scale into
UVEditorController.projectUvBox, projectUvCylinder, and projectUvSphere instead
of fixed constants. Wire the UI to expose X/Y/Z axis selection and a
configurable projection scale, and keep the button labels/handlers aligned with
those new parameters.

Comment thread src/mainwindow.cpp Outdated
Comment thread src/mainwindow.h
Comment thread src/UVEditorController.cpp
Comment thread src/UvProject_test.cpp Outdated
Comment thread src/UvProject.cpp Outdated
Comment thread src/UvProject.cpp
Comment thread src/UvProject.cpp
Comment thread src/UvProject.h
Comment on lines +15 to +21
enum class Mode {
View,
Box,
Cylinder,
Sphere,
ResetBox
};

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 | 🟠 Major | 🏗️ Heavy lift

Missing the required Smart UV projection mode.

The public API still omits the smart/angle-threshold unwrap mode from the linked issue, so this PR cannot expose or test that required workflow yet.

🤖 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/UvProject.h` around lines 15 - 21, The public UvProject::Mode enum is
missing the smart/angle-threshold unwrap option, so add the required Smart UV
projection mode alongside the existing View, Box, Cylinder, Sphere, and ResetBox
values. Update any related UvProject API handling to recognize and route this
new mode wherever Mode is used so the workflow can be exposed and tested end to
end.

Propagate projected UVs across shared vertex buffers, preserve view
camera framing, honor sphere axis and box/cylinder scale, scan all
viewports for camera fallback, and add sphere coverage tests.

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

Copy link
Copy Markdown
Owner Author

Addressed review feedback in 9141a3e:

  • Shared vertex buffers: propagate projected UVs via applyWorkingMeshUv() after projection
  • View mode: skip post-projection normalize so camera framing is preserved
  • Sphere axis: X/Y/Z pole mapping implemented; new test asserts axis 0 vs 1 differ
  • boxScale: applied after normalize via center scaling (box + cylinder)
  • Viewport fallback: scan all dock viewports instead of only .first()
  • mainwindow.h: include OgreMatrix4.h for complete ViewportCameraSnapshot type
  • Status text: emit meshDataChanged() on successful projection
  • Tests: replace M_PI with Ogre::Math::PI; add sphere test

Intentionally not implemented: Smart UV was removed during development (it produced unusable layouts on test assets). Axis/scale UI spinboxes remain deferred — the API accepts params; QML uses defaults for this slice.

@sonarqubecloud

Copy link
Copy Markdown

@fernandotonon
fernandotonon merged commit 9c3a03e into master Jun 29, 2026
21 checks passed
@fernandotonon
fernandotonon deleted the feature/uv-projections-463 branch June 29, 2026 01:13
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.

UV: Slice E — Projection unwrap modes

1 participant