@@ -8348,14 +8348,45 @@ Rectangle {
83488348 spacing: 4
83498349
83508350 property var entityGroups: PropertiesPanelController .animationData ()
8351+ // #854: the clip the arm-space slider targets. Defaults to the last
8352+ // generated clip, but the per-row arm button can point it at ANY
8353+ // animation. Cleared on selection change.
8354+ property string lastGeneratedAnim: " "
8355+ property string armSpaceAnim: " "
8356+ property string armSpaceEntity: " "
83518357
83528358 function refreshAnimData () {
83538359 entityGroups = PropertiesPanelController .animationData ()
83548360 }
83558361
8362+ // #854: point the arm-space slider at a clip. The adjustment
8363+ // STICKS to that clip (so it exports widened) and is independent
8364+ // per clip. Generation always produces a CLEAN clip (slider value
8365+ // never bakes into it), and the slider is seeded with the target
8366+ // clip's ACTUAL current angle — so switching clips shows the truth
8367+ // and a fresh generate shows 0. Closing the panel / reselecting
8368+ // just detaches the slider (no revert; the clip keeps its edit).
8369+ function setArmSpaceTarget (animName , entity ) {
8370+ armSpaceAnim = animName
8371+ armSpaceEntity = entity
8372+ var cur = AnimationControlController .currentArmSpace (animName, entity)
8373+ armSpaceSlider .value = cur
8374+ armSpaceSlider .lastApplied = cur
8375+ }
8376+ function detachArmSpace () {
8377+ armSpaceAnim = " "
8378+ armSpaceEntity = " "
8379+ armSpaceSlider .value = 0
8380+ armSpaceSlider .lastApplied = 0
8381+ }
8382+
83568383 Connections {
83578384 target: PropertiesPanelController
8358- function onSelectionChanged () { refreshAnimData () }
8385+ function onSelectionChanged () {
8386+ lastGeneratedAnim = " "
8387+ detachArmSpace ()
8388+ refreshAnimData ()
8389+ }
83598390 function onAnimationStateChanged () { refreshAnimData () }
83608391 }
83618392 // Morph clips (create/delete/rename) are mesh animations that show
@@ -8385,6 +8416,27 @@ Rectangle {
83858416 verticalAlignment: TextInput .AlignVCenter
83868417 color: PropertiesPanelController .textColor ; font .pixelSize : 11
83878418 clip: true
8419+ // selectByMouse lets a click position the caret AND take
8420+ // keyboard focus — without it a bare TextInput embedded
8421+ // in a QQuickWidget stays unfocused on click (the other
8422+ // working fields in this panel all set it). activeFocus-
8423+ // OnPress is the default but stated for clarity.
8424+ selectByMouse: true
8425+ activeFocusOnPress: true
8426+ // Belt-and-suspenders focus grab: after using the
8427+ // arm-space slider / arm buttons (which take focus and
8428+ // can leave the QQuickWidget's focus on the viewport),
8429+ // a plain click here didn't always re-focus the field.
8430+ // Force it on press and let the event through (accepted
8431+ // = false) so selectByMouse still positions the caret.
8432+ MouseArea {
8433+ anchors .fill : parent
8434+ cursorShape: Qt .IBeamCursor
8435+ onPressed : function (mouse ) {
8436+ genPromptIn .forceActiveFocus ()
8437+ mouse .accepted = false
8438+ }
8439+ }
83888440 Text {
83898441 anchors .verticalCenter : parent .verticalCenter
83908442 visible: ! genPromptIn .text && ! genPromptIn .activeFocus
@@ -8409,8 +8461,19 @@ Rectangle {
84098461 ? " Generating (experimental model)…"
84108462 : " Generating… (first use downloads the motion library)"
84118463 genStatus .isError = false
8412- AnimationControlController .generateMotion (genPromptIn .text , 0.0 ,
8413- useModelChk .checked )
8464+ // Generate a CLEAN clip — arm-space is always an
8465+ // explicit post-adjustment starting from 0, never baked
8466+ // into generation (a leftover slider value silently
8467+ // skewing every new clip is exactly the bug this fixes).
8468+ var gr = AnimationControlController .generateMotion (
8469+ genPromptIn .text , 0.0 , useModelChk .checked ,
8470+ 0.0 )
8471+ if (gr && gr .animation ) {
8472+ lastGeneratedAnim = gr .animation
8473+ // Point the slider at the fresh clip at a neutral 0
8474+ // (reverts any prior target — see setArmSpaceTarget).
8475+ setArmSpaceTarget (gr .animation , gr .entity || " " )
8476+ }
84148477 genBtnBusy = false
84158478 // generateMotion adds an AnimationState synchronously, but
84168479 // it lives on AnimationControlController — the Inspector
@@ -8447,6 +8510,62 @@ Rectangle {
84478510 anchors .verticalCenter : parent .verticalCenter
84488511 }
84498512 }
8513+ // ── Arm space (#854): Mixamo-style widen/tuck post-process ────────
8514+ // Targets `armSpaceAnim` — the last generated clip by default, or
8515+ // any animation the user picks via the per-row arm button below.
8516+ // The op is ABSOLUTE + idempotent, so dragging can re-apply live at
8517+ // each value (no accumulation) and the slider maps straight to the
8518+ // stored angle.
8519+ Row {
8520+ width: parent .width - 16 ; spacing: 6
8521+ visible: armSpaceAnim .length > 0
8522+ Text {
8523+ text: " Arm space"
8524+ color: PropertiesPanelController .textColor ; font .pixelSize : 10
8525+ anchors .verticalCenter : parent .verticalCenter
8526+ width: 62
8527+ elide: Text .ElideRight
8528+ }
8529+ Slider {
8530+ id: armSpaceSlider
8531+ anchors .verticalCenter : parent .verticalCenter
8532+ width: parent .width - 62 - 42 - 12
8533+ from: - 30 ; to: 45 ; value: 0 ; stepSize: 1
8534+ // Live update: adjustArmSpace is absolute+idempotent, so
8535+ // firing it per value while dragging just re-applies (never
8536+ // accumulates). Throttle to whole degrees via stepSize so we
8537+ // rewrite keyframes at most ~75 times across the range.
8538+ property real lastApplied: 0
8539+ onValueChanged: {
8540+ if (armSpaceAnim .length > 0
8541+ && Math .round (value) !== Math .round (lastApplied)) {
8542+ lastApplied = value
8543+ AnimationControlController .adjustArmSpace (
8544+ armSpaceAnim, value, armSpaceEntity)
8545+ }
8546+ }
8547+ // Refresh the Inspector list only on release (the viewport
8548+ // itself updates live inside adjustArmSpace).
8549+ onPressedChanged: { if (! pressed) refreshAnimData () }
8550+ }
8551+ Text {
8552+ text: (armSpaceSlider .value > 0 ? " +" : " " )
8553+ + Math .round (armSpaceSlider .value ) + " °"
8554+ color: PropertiesPanelController .textColor ; font .pixelSize : 10
8555+ anchors .verticalCenter : parent .verticalCenter
8556+ width: 42 ; horizontalAlignment: Text .AlignRight
8557+ }
8558+ }
8559+ // Label showing which clip the slider affects (only when it's not
8560+ // the obvious just-generated one).
8561+ Text {
8562+ visible: armSpaceAnim .length > 0
8563+ width: parent .width - 16 ; wrapMode: Text .Wrap
8564+ text: " ↑ adjusting \" " + armSpaceAnim + " \" (0 = no change). The "
8565+ + " edit stays on this clip; click ↔ again to detach."
8566+ color: PropertiesPanelController .textColor ; opacity: 0.5
8567+ font .pixelSize : 9
8568+ }
84508569 Text {
84518570 id: genStatus
84528571 property bool isError: false
@@ -8955,6 +9074,49 @@ Rectangle {
89559074 }
89569075 }
89579076
9077+ // Arm space (#854) — point the arm-space
9078+ // slider (above) at THIS clip. Works on any
9079+ // skeletal animation, not just generated
9080+ // ones. Highlights when it's the active target.
9081+ Rectangle {
9082+ id: armBtn
9083+ visible: grp .hasSkeleton
9084+ width: 22 ; height: 18 ; radius: 3
9085+ anchors .verticalCenter : parent .verticalCenter
9086+ property bool active: armSpaceAnim === modelData .name
9087+ && armSpaceEntity === grp .entity
9088+ color: active ? PropertiesPanelController .highlightColor
9089+ : armMouse .pressed ? Qt .darker (PropertiesPanelController .headerColor , 1.2 )
9090+ : armMouse .containsMouse ? Qt .lighter (PropertiesPanelController .headerColor , 1.2 )
9091+ : PropertiesPanelController .headerColor
9092+ border .color : PropertiesPanelController .borderColor ; border .width : 1
9093+ Text {
9094+ anchors .centerIn : parent
9095+ text: " ↔" // ↔ widen/tuck
9096+ color: armBtn .active ? " white"
9097+ : PropertiesPanelController .textColor
9098+ font .pixelSize : 12
9099+ }
9100+ ToolTip .visible : armMouse .containsMouse
9101+ ToolTip .delay : 600
9102+ ToolTip .text : " Arm space — retarget the widen/tuck slider to this animation"
9103+ MouseArea {
9104+ id: armMouse; anchors .fill : parent; hoverEnabled: true
9105+ cursorShape: Qt .PointingHandCursor
9106+ onClicked: {
9107+ // Toggle: detach if it's already
9108+ // the target (the clip keeps its
9109+ // edit), else target this clip
9110+ // (seeded with its real angle).
9111+ if (armBtn .active )
9112+ detachArmSpace ()
9113+ else
9114+ setArmSpaceTarget (
9115+ modelData .name , grp .entity )
9116+ }
9117+ }
9118+ }
9119+
89589120 // Bake — resample / reduce every bone track in this
89599121 // animation. Mirrors the curve editor's per-bone Bake
89609122 // dropdown but applies to the whole animation in one
0 commit comments