Skip to content

Conversation

@jasonstratton
Copy link
Contributor

Summary

  • update gizmo drag behavior with incremental node updates and rounding on mouse up for real-time redraw
  • prevent camera pan/orbit from affecting gizmo by filtering mouse events and clearing translation hit state after manipulation
  • queue gizmo render updates asynchronously to reduce UI-thread contention during drag
  • add/update gizmo manipulation tests for drag updates, pan/orbit filtering, hit-state clearing, and origin separation

Test plan

  • Not run locally (environment constraints)
  • Manual: follow README reproduction steps to verify real-time drag redraw and no drift during pan/orbit

Fix for Gizmo drag updates and drift when panning/orbiting
Unit tests
Copilot AI review requested due to automatic review settings January 21, 2026 03:53
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-6738

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses gizmo display issues during drag operations and camera navigation (DYN-6738). The fix implements incremental node updates during dragging for real-time redraw, prevents camera pan/orbit events from interfering with gizmo manipulation, and queues render updates asynchronously to reduce UI thread contention.

Changes:

  • Updated gizmo drag behavior to provide incremental node updates during mouse move and apply final rounding on mouse up
  • Added filtering in MouseDown/MouseUp to ignore events when camera is panning or orbiting, and added ClearHitState call to prevent drift
  • Changed render package updates to use asynchronous queuing during drag operations

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
test/DynamoCoreWpf2Tests/ViewExtensions/GizmoManipulationTests.cs New test file covering drag updates, camera interaction filtering, hit-state clearing, and origin separation
src/DynamoManipulation/TranslationGizmo.cs Added ClearHitState method to reset cached hit axis and plane
src/DynamoManipulation/NodeManipulator.cs Updated MouseDown/MouseUp/MouseMove to filter camera events, implement incremental drag updates, and clear hit state

Comment on lines +448 to +449
dynamic uiNode = inputNode;
uiNode.Value = amount;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The code uses 'dynamic' without type checking, which could fail at runtime if inputNode doesn't have a Value property. Consider adding type validation or using a more specific interface/base type that guarantees the Value property exists.

Copilot uses AI. Check for mistakes.
pointNode.IsSelected = true;

var manipulator = new MousePointManipulator(pointNode, manipulationExtension);
var slider = new DoubleSlider { Value = 1.23456 };
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The test creates a DoubleSlider but doesn't add it to the graph model or verify it's properly connected. Consider adding the slider to the graph through ExecuteCommand to ensure it's in a valid state, or add a comment explaining why this simplified setup is sufficient for the test.

Copilot uses AI. Check for mistakes.
Comment on lines +461 to +462
dynamic uiNode = inputNode;
uiNode.Value = Math.Round((double)uiNode.Value, ROUND_UP_PARAM);
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The code uses 'dynamic' without type checking, which could fail at runtime if inputNode doesn't have a Value property of type double. Consider adding type validation or using a more specific interface/base type.

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +341
var updatedNodes = updatedNodesField.GetValue(manipulator) as HashSet<NodeModel>;
Assert.IsNotNull(updatedNodes, "inputNodesUpdatedDuringDrag should be a HashSet");

updatedNodes.Add(slider);
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The test modifies internal state through reflection but doesn't verify that the slider node is compatible with the manipulator's expected node types. Consider verifying the node is properly registered with the model or documenting why this direct manipulation is safe for testing.

Copilot uses AI. Check for mistakes.
dynamic uiNode = inputNode;
uiNode.Value = Math.Round(amount, ROUND_UP_PARAM);
dynamic uiNode = inputNode;
uiNode.Value = Math.Round(amount, ROUND_UP_PARAM);
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe a good idea here to check if the final value we are setting for uiNode.Value is not the same or close as the current value?

       dynamic uiNode = inputNode;
       var currentValue = (double)uiNode.Value;
       var roundedValue = Math.Round(currentValue, ROUND_UP_PARAM);
       if (Math.Abs(currentValue - roundedValue) > MIN_OFFSET_VAL)
       {
            uiNode.Value = roundedValue;
       }

uiNode.Value = Math.Round((double)uiNode.Value, ROUND_UP_PARAM);
}

inputNodesUpdatedDuringDrag.Clear();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should also be called in the Dispose() method, since the HashSet contains NodeModel references which will not be cleared in case of early exit from MouseUp event.

Copy link
Contributor

@aparajit-pratap aparajit-pratap left a comment

Choose a reason for hiding this comment

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

@jasonstratton I identified the PR that broke this behavior - #12614 in Dynamo 2.14. This bug is a regression and didn't exist prior to version 2.14. Could you take a look at #12614 and identify reasons why those changes could have caused this regression in the first place?

// Skip processing if user is panning or orbiting the camera.
// This prevents the manipulator from capturing mouse events intended for camera navigation,
// including cases where pan/orbit tools use the left mouse button.
if (BackgroundPreviewViewModel is DefaultWatch3DViewModel viewModel)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this condition to check if the backgroundpreview mode is on and not the graph view mode?

}
else
{
var inputNodesToManipulate = InputNodesToUpdateAfterMove(Vector.ByTwoPoints(originBeforeMove, originAfterMove));
Copy link
Contributor

Choose a reason for hiding this comment

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

When will control reach here inside this else?

}
}

//Update gizmo graphics after every camera view change
Copy link
Contributor

@aparajit-pratap aparajit-pratap Jan 23, 2026

Choose a reason for hiding this comment

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

Will this statement ever be true? If there's a camera view change, it doesn't look to me like control will ever reach here in the first place.

// This ensures stale axis/plane hit data doesn't affect pan/orbit movements
if (gizmo is TranslationGizmo translationGizmo)
{
translationGizmo.ClearHitState();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this really necessary, even after preventing mouseup or mousedown events from doing anything after a camera change event?

// Doing this triggers a graph update on scheduler thread
OnGizmoMoved(GizmoInAction, offset);

UpdateInputNodesDuringDrag(offset);
Copy link
Contributor

Choose a reason for hiding this comment

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

Calling this function inside MouseMove seems like the changes in #12614 to improve performance are now being undone since you're now updating the graph for every incremental mouse move event rather than waiting until the mouse button is released.

Copy link
Contributor

@aparajit-pratap aparajit-pratap left a comment

Choose a reason for hiding this comment

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

I wonder if simply preventing camera change events (pan and orbit specifically) from doing any damage in MouseUp will do the trick in fixing this bug. To me, intuitively, it looks like it will.

From what I understand, this bug can be fixed by simply adding this code to the start of MouseDown and MouseUp and calling it a day:

// Skip processing if user is panning or orbiting the camera.
// This prevents the manipulator from capturing mouse events intended for camera navigation,
// including cases where pan/orbit tools use the left mouse button.
if (BackgroundPreviewViewModel is DefaultWatch3DViewModel viewModel)
{
    if (viewModel.IsPanning || viewModel.IsOrbiting)
        return;
}

// Only process left mouse button releases for gizmo manipulation
// Right/middle button releases are for camera navigation (pan/orbit)
if (e.ChangedButton != MouseButton.Left)
{
    return;
}

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.

3 participants