Skip to content

feat(edit): subdivide + fill (Phase 4 topology ops)#324

Merged
fernandotonon merged 4 commits into
masterfrom
feat/subdivide-fill
Apr 28, 2026
Merged

feat(edit): subdivide + fill (Phase 4 topology ops)#324
fernandotonon merged 4 commits into
masterfrom
feat/subdivide-fill

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented Apr 28, 2026

Summary

  • Implements Subdivide (1-to-4 triangle split with T-junction-aware retriangulation of adjacent faces) and Fill (3+ vertex fan-triangulation, closed edge-loop hole fill) — the remaining unchecked items from the Phase 4 topology epic (Epic: Phase 4 — Edit Mode: Topology Tools #259, alongside loop cut).
  • Wires both into EditModeController, the Edit Mode toolbar (⊞ Subdivide, ◆ Fill), and an F shortcut that fills when the selection is fillable and otherwise falls through to Frame-Selection.
  • Includes a drive-by RTSS resource fix that prevented the macOS dev build from launching after this branch (duplicate media/RTShaderLib registration → ItemIdentityException on Ogre/ShadowBlendVP).

Code paths

  • HalfEdgeMesh::subdivideFaces — shared midpoints between selected tris; adjacent unselected tris retriangulate via a splitMask switch (1/2/3 split-edge cases) so we never leak T-junctions.
  • HalfEdgeMesh::fillSelection — submesh-consistent fan-triangulation; rejects cross-submesh inputs, duplicate-of-existing-tri inputs (vertex-set comparison), and out-of-range / duplicate vertex indices. Accepts orphaned vertices (post-deleteFaces hole-cap flow).
  • EditModeController::subdivideSelection — Face mode subdivides selected tris; Edge mode promotes every triangle incident to a selected edge.
  • EditModeController::fillSelection — Vertex mode fan-triangulates the selection; Edge mode runs a degree-2 walk to extract a closed boundary loop before filling.

Tests

  • 23 new HalfEdgeMesh tests (run on every platform): closed-cube subdivide stays manifold, bone-weight blending at midpoints, all splitMask cases, cross-submesh handling, hole-cap with orphaned verts, round-trip safety.
  • 10 new controller-level tests (Linux CI only — Ogre plugin loading skips on macOS): mode dispatch, edge-mode subdivide, vertex-mode fill, undo round-trip, and rejection paths.

Test plan

  • cmake --build build_local --target UnitTests — passes
  • Full HalfEdgeMeshStandalone suite (162 tests) — all green
  • App boots cleanly on macOS arm64 (RTSS fix verified)
  • Linux CI runs the controller-level tests
  • Manual smoke: enter Edit Mode, select a face → toolbar Subdivide adds geometry; delete a face → re-select 3 corners → F fills

Closes

Towards #259 (Subdivide and Fill items checked).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Subdivide topology tool to refine selected faces and edges with automatic midpoint creation and anti-distortion retriangulation
    • Added Fill operation to create new geometry from selected vertices or closed edge loops with fan triangulation
    • Introduced dedicated toolbar buttons for Subdivide and Fill operations in Edit Mode
    • Modified F keyboard shortcut to trigger Fill in Edit Mode (previously framed selection)
  • Bug Fixes

    • Improved shader resource initialization to select the first valid configuration pair

fernandotonon and others added 3 commits April 27, 2026 22:13
Implements the remaining Subdivide and Fill items from issue #259.

HalfEdgeMesh:
  - subdivideFaces: 1-to-4 split with shared midpoints between selected
    faces and T-junction-aware retriangulation of adjacent triangles
    (1/2/3 split-edge cases all handled).
  - fillSelection: 3-N vertex fan-triangulation for vertex fills and
    closed boundary edge loops; refuses cross-submesh, duplicate-tri,
    and orphaned-only inputs.

EditModeController:
  - subdivideSelection wires Face mode (selected tris) and Edge mode
    (incident tris of selected edges) to the HE primitive, captures
    midpoint positions to re-select after the round-trip pack, and
    pushes one EditMeshTopologyCommand for undo.
  - fillSelection wires Vertex mode (set order = fan order) and Edge
    mode (closed-loop detection via degree-2 walk).

UI:
  - Two new toolbar buttons (Subdivide ⊞, Fill ◆) with mode-aware
    enable/visibility.
  - F shortcut fills in edit mode when the selection is fillable;
    otherwise falls through to the existing Frame-Selection behavior.

Tests:
  - 15 new HalfEdgeMesh standalone tests covering both ops, including
    T-junction handling, UV interpolation, cross-submesh rejection,
    and round-trip safety.
  - 7 new controller tests guarded by tryInitOgre (skip on macOS,
    run on Linux CI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
addRTSSResources() registered up to 4 candidate directories, deduplicated
only by canonical path. On macOS dev builds the bundle's interior media/
and the dev-copy at bin/media/ are distinct paths with identical contents,
so both got registered. Ogre re-parses .program scripts per resource
location, and the second pass over ShadowVolumeExtude.program raised
ItemIdentityException on the duplicate Ogre/ShadowBlendVP GpuProgram,
aborting startup.

Pick the FIRST existing RTShaderLib + sibling Main pair and stop. Both
copies hold the same files, so the order doesn't matter functionally —
this just avoids the double-registration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HalfEdgeMesh tests (+8):
  - subdivide every cube face stays closed-manifold
  - subdivide preserves blended bone weights at midpoints
  - splitMask=2 path verified on the quad neighbor
  - cross-submesh subdivide leaves submesh 1 untouched
  - non-adjacent multi-face selection on a cube
  - fill caps a deleted cube face back to closed
  - fill accepts orphaned vertices (post-delete hole-cap flow)
  - fill round-trip safe through toEditableMesh/buildFromEditableMesh

Controller tests (+3):
  - subdivideSelection in edge mode promotes incident faces
  - fillSelection in vertex mode produces a new triangle
  - fillSelection pushes an undo command

Docs:
  - CLAUDE.md keyboard shortcut line notes F's dual role and X/Ctrl+X
    in Edit Mode
  - New "Edit Mode (Phase 4 topology)" section summarising the
    EditModeController + HalfEdgeMesh op set, with a targeted note on
    Subdivide and Fill

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

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@fernandotonon has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 33 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7bf909a-6cd8-42ec-b377-37fd8871ce1c

📥 Commits

Reviewing files that changed from the base of the PR and between acfceee and 9762be4.

📒 Files selected for processing (4)
  • src/EditModeController.cpp
  • src/HalfEdgeMesh.cpp
  • src/HalfEdgeMesh_test.cpp
  • src/mainwindow.cpp
📝 Walkthrough

Walkthrough

Two new topology-editing operations—Subdivide and Fill—are introduced to the Edit Mode system with implementations across HalfEdgeMesh for mesh mutation, EditModeController for orchestration, UI toolbar integration, and comprehensive test coverage. A minor RTShaderHelper fix is also included.

Changes

Cohort / File(s) Summary
Documentation
CLAUDE.md
Updated keyboard shortcut bindings to mark F and X as Edit Mode-contextual; introduced new Edit Mode (Phase 4 topology) section describing EditModeController, HalfEdgeMesh, and two specific mesh operations: Subdivide and Fill.
Core Topology Mesh Operations
src/HalfEdgeMesh.h, src/HalfEdgeMesh.cpp
Added subdivideFaces() for midpoint subdivision with T-junction avoidance via edge split masks and retriangulation; added fillSelection() for hole-capping via fan triangulation with submesh and duplicate-face validation.
Edit Mode Controller
src/EditModeController.h, src/EditModeController.cpp
Introduced subdivideSelection() and fillSelection() as invokable topology operations that resolve selections to mesh indices, orchestrate HalfEdgeMesh operations, update entity buffers/normals, rebuild selections on midpoints/vertices, and push undo commands.
Test Coverage
src/HalfEdgeMesh_test.cpp, src/EditModeController_test.cpp
Added comprehensive gtest suites validating subdivideFaces() and fillSelection() topology correctness (manifoldness, attribute interpolation, edge case rejection) and EditModeController dispatcher logic (mode preconditions, buffer updates, undo integration).
UI Toolbar Integration
src/mainwindow.cpp
Added Subdivide and Fill toolbar buttons invoking the new topology operations; remapped F keyboard shortcut to Fill in edit mode when selection is eligible, with fallback to frame-selection behavior.
Resource Initialization Fix
src/RTShaderHelper.cpp
Simplified RTShaderLib resource registration to select and register only the first valid RTShaderLib/Main directory pair instead of registering all deduplicated candidates.

Sequence Diagram

sequenceDiagram
    participant User
    participant MainWindow as UI / Toolbar
    participant EMC as EditModeController
    participant HEM as HalfEdgeMesh
    participant EM as EditableMesh
    participant Undo as Undo System

    User->>MainWindow: Click Subdivide / Press F
    MainWindow->>EMC: subdivideSelection() / fillSelection()
    
    EMC->>EMC: Resolve selection<br/>(faces or edge→face mapping)
    EMC->>HEM: subdivideFaces(faceIndices)<br/>or fillSelection(vertexIndices)
    
    HEM->>HEM: Compute midpoints / fan triangulation<br/>Rebuild topology (edges/twins/boundaries)
    HEM-->>EMC: Return new vertex indices<br/>or triangle count
    
    EMC->>EM: Write modified topology<br/>back into EditableMesh
    EMC->>EMC: Recompute normals<br/>Resize Ogre buffers
    EMC->>EMC: Rebuild selection<br/>(re-identify midpoints/vertices)
    
    EMC->>Undo: Push EditMeshTopologyCommand<br/>with "Subdivide"/"Fill" label
    
    EMC->>MainWindow: Emit selection/mesh<br/>change signals
    MainWindow->>User: Update overlays & viewport
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰✨ With scissors sharp, we subdivide the mesh,
Each edge splits midway, fresh and clean and mesh,
And where there's holes, we fan those vertices wide,
Four triangles bloom where emptiness did hide—
Phase Four's topology, a rabbit's delight! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.00% 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 'feat(edit): subdivide + fill (Phase 4 topology ops)' clearly and concisely summarizes the main changes—two new topology editing operations (Subdivide and Fill) added to the Edit Mode feature.
Description check ✅ Passed The PR description comprehensively covers the required template sections: Summary explains the implementation and related fixes, Technical Details lists code paths, and comprehensive test coverage information is provided.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/subdivide-fill

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

❤️ Share

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

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acfceee98d

ℹ️ 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 +3043 to +3045
// Every vertex on a closed loop has degree 2.
for (const auto& [v, nbrs] : adj) {
if (nbrs.size() != 2) return {};
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 Require boundary edges before edge-mode fill

buildClosedEdgeLoop only validates graph degree/connectivity, so any closed cycle of selected edges is treated as fillable. In edge mode this allows filling loops that are not boundaries (for example, the perimeter of an already-existing face on a closed mesh), and HalfEdgeMesh::fillSelection will then append overlapping triangles, creating non-manifold/duplicated geometry instead of capping a hole. The loop construction needs a boundary-edge check against the live mesh (e.g., each selected edge has exactly one incident face) before calling fill.

Useful? React with 👍 / 👎.

Comment thread src/mainwindow.cpp Outdated
Comment on lines +1350 to +1352
editCtrl->fillSelection();
event->accept();
return;
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 Fall back to frame-selection when fill operation is rejected

The F shortcut unconditionally accepts the event and returns whenever the selection count looks fillable, but it ignores whether fillSelection() actually succeeded. For invalid loops/rejected fills (e.g., 3 edges that do not form a closed boundary), this makes F do nothing instead of falling through to frame-selection as intended. Gate the early return on fillSelection() > 0 (or equivalent success check).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

93-112: 🛠️ Refactor suggestion | 🟠 Major

Use macBundlePath() instead of manual ../.. traversal on macOS.

This segment still hardcodes bundle-relative traversal from applicationDirPath(), which is brittle across packaging layouts. Please switch macOS candidate roots to macBundlePath()-based paths.

As per coding guidelines: "Use macBundlePath() for macOS to get .app bundle root (not Contents/MacOS/). Ogre resource paths in resources.cfg resolve relative to this bundle root".

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

In `@src/RTShaderHelper.cpp` around lines 93 - 112, Replace the manual ../.. and
../../../ traversals in RTShaderHelper.cpp (where you build macOS candidates
using QCoreApplication::applicationDirPath()) with macBundlePath() roots:
instead of appDir + "/../../media/RTShaderLib" and appDir +
"/../../../media/RTShaderLib" add candidates based off macBundlePath(), e.g.
macBundlePath() + "/media/RTShaderLib" (bundle-contained media) and
macBundlePath() + "/../media/RTShaderLib" (adjacent install/dev layout); keep
the other non-mac candidates and the candidates list logic unchanged.
🧹 Nitpick comments (3)
src/EditModeController_test.cpp (1)

1703-1879: Edge-mode fill still needs a controller test.

The new suite exercises vertex-mode fill, but it never hits the controller-only path that walks a selected boundary-edge loop into an ordered vertex ring. A regression there would still pass these tests and the lower-level HalfEdgeMesh coverage.

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

In `@src/EditModeController_test.cpp` around lines 1703 - 1879, The test suite
lacks a controller-level test for edge-mode fill (the code path that walks a
selected boundary-edge loop into an ordered vertex ring), so add a new TEST_F
(e.g., FillSelectionEdgeModeHandlesBoundaryLoop) that: obtains the singleton
EditModeController::instance(), calls enterEditMode(), sets selection mode to
EditModeController::EdgeMode, selects a real boundary edge via selectEdge(va,vb)
using an edge pulled from extractEntityBuffers(m_entity, pos, tris) (like other
tests do to pick a safe edge), asserts that ctrl->fillSelection() returns > 0
(or the expected triangle count change), and finally verifies the entity buffers
via extractEntityBuffers to ensure triangles increased or the fill effect
occurred; follow the patterns in FillSelectionVertexModeProducesNewTriangle and
SubdivideSelectionEdgeModePromotesIncidentFaces for setup and validation.
src/HalfEdgeMesh_test.cpp (1)

4406-4425: Rename this test or expand it to actually cover all split-mask branches.

The current fixture only drives one neighbor split-mask case on the quad, so the title/comment overstates coverage for splitMask handling. That makes the suite look stronger than it is.

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

In `@src/HalfEdgeMesh_test.cpp` around lines 4406 - 4425, The test named
SubdivideFacesHandlesAllSplitMaskPaths (TEST(HalfEdgeMeshStandalone,
SubdivideFacesHandlesAllSplitMaskPaths)) claims to exercise all splitMask
branches but only drives one neighbor case; either rename the test to accurately
reflect that it only tests a single splitMask path or expand it to cover all
cases by creating fixtures that trigger splitMask values 1,2,4 and combinations
(e.g., build multiple small meshes or adjust selections on
makeQuadMesh/constructed HalfEdgeMesh so subdivideFaces(...) exercises neighbor
edges that produce each splitMask branch), then assert expected face
counts/structure and keep calls to he.subdivideFaces, he.validate, and
activeFaceCount(he) for each scenario.
src/EditModeController.cpp (1)

2993-3004: Tolerance inconsistency: 1e-10f vs 1e-8f used elsewhere.

The position-matching tolerance here (1e-10f) is 100× tighter than findGlobalVertsByPosition (1e-8f) and other similar lookups in this file. While tighter is generally safer for subdivision midpoints (which should be exact), the inconsistency could cause subtle mismatches if floating-point rounding occurs during the HE → EditableMesh round-trip.

Consider using a named constant shared across all position-based vertex lookups for consistency.

♻️ Suggested constant
+namespace {
+constexpr float kPositionMatchEps2 = 1e-8f; // squared distance tolerance for position-based vertex matching
+} // namespace

 // In subdivideSelection and findGlobalVertsByPosition:
-if (sub.vertices[li].position.squaredDistance(tgt) < 1e-10f) {
+if (sub.vertices[li].position.squaredDistance(tgt) < kPositionMatchEps2) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/EditModeController.cpp` around lines 2993 - 3004, The tolerance 1e-10f
used when comparing sub.vertices[li].position to midpointPositions is
inconsistent with other lookups (e.g., findGlobalVertsByPosition) which use
1e-8f; replace the hardcoded 1e-10f with a shared named constant (e.g.,
POSITION_MATCH_TOLERANCE) declared alongside other position-lookup utilities so
all position-based comparisons (including the loop that uses subs,
midpointPositions and the m_selectedVertices insertion) use the same tolerance
(set to 1e-8f) to avoid subtle mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/HalfEdgeMesh.cpp`:
- Around line 4764-4789: The duplicate-triangle check currently only runs when n
== 3; update it to also guard against any triangle that the fan will emit for n
> 3 by checking, for each i in 1..n-2, whether the triangle (vertexIndices[0],
vertexIndices[i], vertexIndices[i+1]) already exists; use
facesAroundVertex(vertexIndices[0]) and faceVertices(f) to detect an identical
set of three vertex indices (order-insensitive) and return 0 if any would
duplicate an existing face before calling appendTriangle, keeping the existing
triCount/appendTriangle logic intact.

In `@src/mainwindow.cpp`:
- Around line 1341-1353: The F-key handling accepts the event whenever fillable
is true, but fillSelection() can return 0 on failure which still swallows the
frame-selection fallback; change the logic in the edit-mode branch (use
editCtrl, selectionMode, EditModeController::VertexMode/EdgeMode,
selectedVertexCount/selectedEdgeCount, fillSelection(), event->accept()) to call
fillSelection(), capture its return value, and only call
SentryReporter::addBreadcrumb, event->accept(), and return when fillSelection()
indicates success (non-zero); if fillSelection() returns 0, do not accept the
event so the normal frame-selection fallback can run.

---

Outside diff comments:
In `@src/RTShaderHelper.cpp`:
- Around line 93-112: Replace the manual ../.. and ../../../ traversals in
RTShaderHelper.cpp (where you build macOS candidates using
QCoreApplication::applicationDirPath()) with macBundlePath() roots: instead of
appDir + "/../../media/RTShaderLib" and appDir + "/../../../media/RTShaderLib"
add candidates based off macBundlePath(), e.g. macBundlePath() +
"/media/RTShaderLib" (bundle-contained media) and macBundlePath() +
"/../media/RTShaderLib" (adjacent install/dev layout); keep the other non-mac
candidates and the candidates list logic unchanged.

---

Nitpick comments:
In `@src/EditModeController_test.cpp`:
- Around line 1703-1879: The test suite lacks a controller-level test for
edge-mode fill (the code path that walks a selected boundary-edge loop into an
ordered vertex ring), so add a new TEST_F (e.g.,
FillSelectionEdgeModeHandlesBoundaryLoop) that: obtains the singleton
EditModeController::instance(), calls enterEditMode(), sets selection mode to
EditModeController::EdgeMode, selects a real boundary edge via selectEdge(va,vb)
using an edge pulled from extractEntityBuffers(m_entity, pos, tris) (like other
tests do to pick a safe edge), asserts that ctrl->fillSelection() returns > 0
(or the expected triangle count change), and finally verifies the entity buffers
via extractEntityBuffers to ensure triangles increased or the fill effect
occurred; follow the patterns in FillSelectionVertexModeProducesNewTriangle and
SubdivideSelectionEdgeModePromotesIncidentFaces for setup and validation.

In `@src/EditModeController.cpp`:
- Around line 2993-3004: The tolerance 1e-10f used when comparing
sub.vertices[li].position to midpointPositions is inconsistent with other
lookups (e.g., findGlobalVertsByPosition) which use 1e-8f; replace the hardcoded
1e-10f with a shared named constant (e.g., POSITION_MATCH_TOLERANCE) declared
alongside other position-lookup utilities so all position-based comparisons
(including the loop that uses subs, midpointPositions and the m_selectedVertices
insertion) use the same tolerance (set to 1e-8f) to avoid subtle mismatches.

In `@src/HalfEdgeMesh_test.cpp`:
- Around line 4406-4425: The test named SubdivideFacesHandlesAllSplitMaskPaths
(TEST(HalfEdgeMeshStandalone, SubdivideFacesHandlesAllSplitMaskPaths)) claims to
exercise all splitMask branches but only drives one neighbor case; either rename
the test to accurately reflect that it only tests a single splitMask path or
expand it to cover all cases by creating fixtures that trigger splitMask values
1,2,4 and combinations (e.g., build multiple small meshes or adjust selections
on makeQuadMesh/constructed HalfEdgeMesh so subdivideFaces(...) exercises
neighbor edges that produce each splitMask branch), then assert expected face
counts/structure and keep calls to he.subdivideFaces, he.validate, and
activeFaceCount(he) for each scenario.
🪄 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: 94499c0f-9449-45a0-b956-14cd823a03d8

📥 Commits

Reviewing files that changed from the base of the PR and between d24e0e8 and acfceee.

📒 Files selected for processing (9)
  • CLAUDE.md
  • src/EditModeController.cpp
  • src/EditModeController.h
  • src/EditModeController_test.cpp
  • src/HalfEdgeMesh.cpp
  • src/HalfEdgeMesh.h
  • src/HalfEdgeMesh_test.cpp
  • src/RTShaderHelper.cpp
  • src/mainwindow.cpp

Comment thread src/HalfEdgeMesh.cpp Outdated
Comment thread src/mainwindow.cpp
Three issues from the PR review:

1. Codex P1: buildClosedEdgeLoop only checked graph degree, so any
   closed cycle of selected edges (e.g. the perimeter of an existing
   face) passed through. Filling such a cycle stacked overlapping
   triangles on the live geometry. Added a per-edge boundary check
   (each selected edge must touch exactly one face) before the loop
   walk, refusing interior-edge cycles upfront.

2. CodeRabbit Major: HalfEdgeMesh::fillSelection's duplicate-triangle
   check was gated on n == 3, leaving n > 3 fans free to recreate
   existing tris on their first sub-triangle. Replaced the gate with a
   per-fan-triangle check: every (apex, v_i, v_{i+1}) tri is checked
   against existing faces incident to the apex. Added a regression
   test (FillSelectionRejectsLargerFanThatDuplicatesExistingTri).

3. Codex P2 / CodeRabbit Major: the F shortcut accepted the event
   whenever the selection looked fillable, even when fillSelection()
   returned 0 (e.g. interior edge loop after fix #1, or duplicate-
   triangle rejection after fix #2). Now only swallows the keypress on
   a non-zero return — so F still falls back to Frame-Selection when
   the fill is rejected.

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

@fernandotonon fernandotonon merged commit 586e5ee into master Apr 28, 2026
19 checks passed
@fernandotonon fernandotonon deleted the feat/subdivide-fill branch April 28, 2026 06:10
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