Skip to content

feat(morph): A3b — Inspector add / rename / delete for morph targets#579

Merged
fernandotonon merged 2 commits into
masterfrom
feat/morph-slice-a3b-inspector-ui
May 17, 2026
Merged

feat(morph): A3b — Inspector add / rename / delete for morph targets#579
fernandotonon merged 2 commits into
masterfrom
feat/morph-slice-a3b-inspector-ui

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented May 17, 2026

Follow-up to #578. Surfaces the C++ authoring API from morph slice A3 in the existing Morph Targets subgroup in qml/PropertiesPanel.qml. After this PR the morph epic (#518) is end-to-end: import → list → set weight → save current edit as new target → rename → delete → export, all with undo.

What ships

"+ Add…" button (top row, next to "Reset all")

Opens a built-in Qt Quick Controls Popup with a name field, calls MorphAnimationManager.addMorphTargetFromCurrentEdit. Deliberately uses primitives — no custom dialog component, no new imports — because A5's QML changes hit cross-engine initialization issues we never fully isolated and I want to keep this slice's blast radius minimal.

Inline rename (double-click name)

Mirrors the per-animation rename UX a few sections above. Double-click → hidden TextInput becomes visible → onEditingFinished calls renameMorphTarget. Empty / whitespace-only / unchanged names are filtered client-side before the call. Server-side collision rejection still applies (no shadowing).

Per-row "×" delete

Pushes DeleteMorphTargetCommand through the shared UndoManager, so Ctrl+Z restores the pose + animation.

Why this is small and safe

  • No new QML imports — MorphAnimationManager was already imported by the existing subgroup.
  • No new Connections blocks — the existing one already refreshes target list and weight readouts on morphTargetsChanged / morphWeightChanged, so add / rename / delete flow back into the UI without new wiring.
  • All three new buttons are pure pass-throughs to existing Q_INVOKABLE methods on MorphAnimationManager. The C++ surface was already covered by the 16 tests merged in feat(morph): sub-slice A3 — authoring (add from edit, rename, delete) #578.

#518 status

Sub-slice Status
A1 — Importer + manager + CLI list shipped (#569)
A2 — Inspector subgroup shipped (#570)
A3 — Authoring data layer shipped (#578)
A3b — Inspector authoring UI this PR
A4a — glTF export shipped (#573)
A4b — FBX export shipped (#575)
A5 — Controller API shipped (#576)
A5b — Dope-sheet UI follow-up
A6 — MCP tools shipped (#571)

Once A3b lands the epic is feature-complete except for the dope-sheet morph band (the deferred UI from A5).

Test plan

  • CI green (unit-tests-linux + all platforms)
  • Manual: import an FBX with blend shapes → enter edit-mode → translate a vertex → click "+ Add…" → name "Test" → confirm new entry appears with slider → double-click name to rename → click × to delete → Ctrl+Z restores it.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added "Add" button to create new morph targets/blend shapes in the Animations panel
    • Morph target names now support direct editing via double-click-to-rename functionality
    • Added delete control to remove morph targets from the panel

Review Change Stack

Follow-up to #578. Wires the C++ authoring API from A3 into the
existing Morph Targets subgroup in qml/PropertiesPanel.qml:

- **"+ Add…" button** next to "Reset all" — opens a built-in Qt
  Quick Controls Popup with a name field, calls
  `addMorphTargetFromCurrentEdit`. The Popup deliberately avoids
  custom dialog components: A5's QML changes hit cross-engine
  initialization issues we never fully isolated, so this slice
  sticks to primitives with no singleton dependencies beyond the
  already-imported MorphAnimationManager.
- **Inline rename** — double-click a target name to edit in place,
  matching the per-animation rename UX a few sections above. Empty
  / whitespace-only / unchanged names are filtered client-side
  before the call. Server-side collision rejection still applies.
- **Per-row "×" delete** — pushes DeleteMorphTargetCommand through
  UndoManager so Ctrl+Z restores the pose.

The existing `Connections` block on the manager already refreshes
the target list and weight readouts on `morphTargetsChanged` /
`morphWeightChanged`, so add / rename / delete all flow back into
the UI for free.

After this slice the morph epic (#518) is end-to-end:
import → list → set weight → save current edit as new target →
rename → delete → export — all with undo. Closes the authoring
acceptance criterion in the issue.

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

coderabbitai Bot commented May 17, 2026

Warning

Rate limit exceeded

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

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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: ebd0feb2-af8f-4162-acd3-eb6956bb5c9e

📥 Commits

Reviewing files that changed from the base of the PR and between 860eccd and 7b4405a.

📒 Files selected for processing (1)
  • qml/PropertiesPanel.qml
📝 Walkthrough

Walkthrough

QML UI layer adds morph-target creation, renaming, and deletion controls to the Animations panel. A header "+ Add…" button opens a name-entry popup, inline row edits use double-click TextInput renaming, and each target gains a delete button—all delegating state changes to MorphAnimationManager.

Changes

Morph Target CRUD Operations

Layer / File(s) Summary
Add morph target dialog
qml/PropertiesPanel.qml
Header row adds "+ Add…" button and layout adjustment; new modal Popup with TextField captures morph target name, Save confirms via MorphAnimationManager.addMorphTargetFromCurrentEdit(), Cancel dismisses.
Per-target rename and delete controls
qml/PropertiesPanel.qml
Each morph target row replaces static Text name with double-click-to-edit TextInput, committing edits via MorphAnimationManager.renameMorphTarget() or canceling with Escape; new delete button (×) calls MorphAnimationManager.deleteMorphTarget() for removal.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • fernandotonon/QtMeshEditor#518: QML UI changes directly implement morph-target add/rename/delete controls that call MorphAnimationManager methods for managing blend-shape authoring.

Possibly related PRs

  • fernandotonon/QtMeshEditor#578: Main PR's QML morph-target UI (add/rename/delete buttons) directly invokes the retrieved PR's newly added MorphAnimationManager QML-invokable methods and undoable morph authoring commands.
  • fernandotonon/QtMeshEditor#570: Both PRs update qml/PropertiesPanel.qml's Morph Targets section by adding/rewiring UI rows bound to MorphAnimationManager, with this PR further extending that functionality to support add/rename/delete interactions.
  • fernandotonon/QtMeshEditor#576: Main PR adds/renames/deletes morph targets in qml/PropertiesPanel.qml via MorphAnimationManager, while retrieved PR adds AnimationControlController::allMorphRows() and refreshes morph-band UI from the same MorphAnimationManager signals.

Poem

🐰 A button blooms with "+ Add…" cheer,
Popups whisper names so clear,
Double-click to edit with grace,
Delete flows smooth—no trace!
Morph targets dance in the UI sphere! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding inspector UI for morph target authoring (add/rename/delete functionality) within the existing Morph Targets section.
Description check ✅ Passed The description is comprehensive and well-structured, covering the summary, shipped features, implementation rationale, and test plan. While not strictly following the template sections (Features/Bugfixes), it provides clear technical details and context about what ships.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/morph-slice-a3b-inspector-ui

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: 860eccd918

ℹ️ 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 qml/PropertiesPanel.qml Outdated
Comment on lines +4465 to +4466
MorphAnimationManager.addMorphTargetFromCurrentEdit(n)
addNamePopup.close()
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 Keep add popup open when creation call fails

Handle the return value from MorphAnimationManager.addMorphTargetFromCurrentEdit(n) before closing the popup. That method returns false for common failure cases (for example duplicate names or not being in edit mode), but this code closes the dialog unconditionally, so users lose their input and get no feedback while nothing is created.

Useful? React with 👍 / 👎.

Comment thread qml/PropertiesPanel.qml Outdated
Comment on lines +4552 to +4556
if (trimmed.length > 0 && trimmed !== modelData)
MorphAnimationManager.renameMorphTarget(modelData, trimmed)
visible = false
}
Keys.onEscapePressed: visible = false
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 Prevent Escape from committing morph target rename

Pressing Escape is intended to cancel inline rename, but Keys.onEscapePressed only hides the TextInput; hiding causes focus loss, and onEditingFinished then still executes the rename path when text changed. This means users can accidentally rename a target even when they hit Escape to cancel.

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

🤖 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 4372-4395: The "+ Add…" control must be disabled when
add-from-edit is unavailable: use a controller boolean (e.g.
PropertiesPanelController.addFromEditAvailable) to drive the enabled state and
visual disabled styling on the Rectangle (addBtn) and MouseArea (addMa), e.g.
set addBtn.enabled: PropertiesPanelController.addFromEditAvailable (or
addMa.enabled likewise), change color/opacity and cursorShape when disabled, and
remove/guard the addNamePopup.open() call so it only runs when the flag is true;
apply the same change to the other "+ Add…" block referenced (lines 4462-4467)
so both instances respect PropertiesPanelController.addFromEditAvailable.
- Around line 4550-4557: Escape currently hides the TextInput via
Keys.onEscapePressed which causes onEditingFinished to run and trigger
MorphAnimationManager.renameMorphTarget; fix by introducing a cancel flag on the
TextInput (e.g., skipRename) and set skipRename = true inside
Keys.onEscapePressed before setting visible = false, then change the
onEditingFinished handler to return early if skipRename is true (clear
skipRename after checking) so Escape cancels without renaming; reference the
existing onEditingFinished, Keys.onEscapePressed,
MorphAnimationManager.renameMorphTarget, modelData and trimmed identifiers when
making the change.
🪄 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: 87506ffd-aef7-4481-b6e6-5cb7cb60eeb6

📥 Commits

Reviewing files that changed from the base of the PR and between 22123fe and 860eccd.

📒 Files selected for processing (1)
  • qml/PropertiesPanel.qml

Comment thread qml/PropertiesPanel.qml
Comment thread qml/PropertiesPanel.qml
Addresses three findings from Codex + CodeRabbit on PR #579:

## Codex P2 — keep Add popup open on failure
`MorphAnimationManager.addMorphTargetFromCurrentEdit` returns false
for several legitimate failure cases (duplicate name, not in edit
mode, no vertex moved vs bind baseline). The popup was closing
unconditionally, so users lost their typed name with no feedback
about why nothing was created.

Fix: branch on the return value. On true → close. On false → leave
the popup open and surface an inline red error message ("Couldn't
save: name already in use, or no vertex was edited."). Empty-name
and edit-mode-required failures get their own explicit messages.

## Codex P2 — Escape must not commit rename
Pressing Escape on the inline morph-rename input previously fired
`Keys.onEscapePressed: visible = false`, but the hide path causes
focus loss which then re-fires `onEditingFinished` — committing the
rename the user tried to cancel.

Fix: add a `cancelled` boolean property set by Escape and checked at
the top of `onEditingFinished`; the cancel path resets the flag and
returns without calling renameMorphTarget.

## CodeRabbit minor — visibly disable Add outside edit mode
The "+ Add…" button stayed enabled even when not in edit mode,
where `addMorphTargetFromCurrentEdit` always returns false.

Fix: bind a `canAddFromEdit: EditModeController.editModeActive`
property on the button; control opacity, hover highlight, MouseArea
enabled state, cursor shape (PointingHand vs Forbidden), and add a
tooltip that says "Enter Edit Mode (Tab) to add morph targets from
current edit." The Save handler in the popup also re-checks
`editModeActive` defensively — protects against the edge case where
the user opens the popup mid-edit, exits edit mode, then clicks Save.

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

@fernandotonon fernandotonon merged commit 90344d8 into master May 17, 2026
20 checks passed
@fernandotonon fernandotonon deleted the feat/morph-slice-a3b-inspector-ui branch May 17, 2026 21:24
fernandotonon added a commit that referenced this pull request May 17, 2026
Final sub-slice of #518. The C++ data API (`allMorphRows`) shipped
in #576 as a Q_INVOKABLE on AnimationControlController; this PR
wires it into the dope-sheet view.

## What ships

- New `morphRows` property on the dope-sheet root, bound to
  `AnimationControlController.allMorphRows()`. Refreshed via the
  existing `onSelectionChanged` handler so a clip change rebuilds
  both bone and morph rows in lockstep.
- `rowsView` (bone ListView) bottom anchor switches to
  `morphBand.top` when the morph band is visible. Bone-only
  entities → band collapses to `height=0` → bone list draws full
  height as before.
- New `morphBand` Rectangle at the bottom: header row showing
  "Morph Targets (N)", then one row per pose with the target name
  on the left and `#88ccff` diamond markers at each keyframe time
  on the right. Diamonds share the bone-row timeline math
  (`pxPerSec`, `viewStart`) so they line up vertically.

## Why this is small and safe

The original A5 (PR #576's first commit) deterministically crashed
unrelated MainWindow / MCPServer visibility-toggle suites in CI.
Root cause was never fully isolated but the suspicion was an
`import PropertiesPanel 1.0` triggering a different QML singleton
chain during MainWindow construction.

This PR keeps the discipline that worked for A3b:
- **No new QML imports.** `AnimationControl 1.0` was already
  imported. `allMorphRows()` was deliberately put on
  AnimationControlController (not MorphAnimationManager) for
  exactly this reason.
- **No new `Connections` blocks.** The existing one already fires
  on the signals we care about.
- **No MouseAreas, no selection, no drag** on morph diamonds —
  this is strictly read-only display. Interactive morph keyframes
  (selection, multi-select, drag, copy/paste) need their own
  controller surface and land in a separate PR.

## #518 status

After this slice the issue is feature-complete and ready to close:

| Sub-slice | Status |
|-|-|
| A1 — Importer + manager + CLI list | shipped (#569) |
| A2 — Inspector subgroup | shipped (#570) |
| A3 — Authoring data layer | shipped (#578) |
| A3b — Inspector authoring UI | shipped (#579) |
| A4a — glTF export | shipped (#573) |
| A4b — FBX export | shipped (#575) |
| A5 — Controller API | shipped (#576) |
| A5b — Dope-sheet UI | **this PR** |
| A6 — MCP tools | shipped (#571) |

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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