Grasshopper C# - Instantiate Panel On Wire Drag Release

Hello,

I’m trying to get the current mouse canvas position via python and while I think I’m calling the correct methods I can’t figure out how to cast the IronPython.Runtime.Types.ReflectedProperty or IronPython.Runtime.BuiltinFunction (.GetValue method). The method I’m calling says it returns a PointF struct but I’m not sure how to get the actual XY point values as calling .X or .Y on the position (or and index 0, or 1) does not seem to yield a number value I would expect from a point2d for instance.

Getting the windows cursor position is working just fine but I need the GH canvas position.

Thanks so much for the help!

Graph Space:

Code Thus Far:

import Grasshopper as gh
import System.Drawing as sd
import System.Windows.Forms as Forms

# Function to add a panel at the specified location
def add_panel(x, y):
    # Create a Panel component at the specified (x, y) location
    panel = ghenv.Component.OnPingDocument().AddObject("Panel", x, y, False)
    
    # Output the (x, y) coordinates of the created panel in canvas space
    return ghenv.Component.Attributes.Bounds.Location

# Get the current component position in the Grasshopper canvas space
current_component_position = ghenv.Component.Attributes.Bounds.Location

# Output the current component position in the O output
O = current_component_position

# Get the cursor position from Windows
Wp = Forms.Cursor.Position

# Get the cursor position from the GH Canvas Mouse Event
mouse_loc_x = gh.GUI.GH_CanvasMouseEvent.CanvasX.GetValue
mouse_loc_y = gh.GUI.GH_CanvasMouseEvent.CanvasY.GetValue

mouse_loc = (mouse_loc_x,mouse_loc_y)

cursor_position = mouse_loc

# Get The X,Y Canvas Position Of The Mouse Cursor
Cp = cursor_position
print Cp

## Check if the "P" key is pressed
#if ghenv.Component.OnPingDocument().KeyDown.IsKeyDown(Forms.Keys.P):
#    # "P" key is pressed, create a Panel component at the Cp position
#    ghenv.Component.OnPingDocument().CreateDefaultAttributes()
#    panel_guid = ghenv.Component.OnPingDocument().AddObject("Panel", Cp.X, Cp.Y, False)
#    
#    # Output the (x, y) coordinates of the created panel in canvas space
#    add_panel(Cp.X, Cp.Y)

20230823_Add_Panel_On_Wire_Drag_01a.gh (6.7 KB)

This might help:

Edit: Also this:

1 Like

Thanks @AndersDeleuran , I looked into these methods and while super handy (and I’m going to use them for other applications, thank you!) my goal is to retrieve the GH mouse position and mouse event for the below outlined reason:

Here’s what I’m trying to achieve as a workflow research experiment.

Have a component that listens to a GH mouse event.

Psuedo Code:

On Mouse Wire Drag (Component.Output)

Get Drag Location End

Add GH Object “Panel” with default attributes at Drag Location End

Connect Component.Output to “Panel”.Input[0]

The idea is that on any component when you drag out a wire and release the mouse in blank canvas space it will spawn a connected panel by default at the released mouse location.

So far I’m only successfully getting the cursor positions.

Code Thus Far:

import Grasshopper as gh
import System.Drawing as sd
import System.Windows.Forms as Forms

gh_GUI = gh.GUI
gh_canvas = gh.GUI.Canvas.GH_Canvas()
gh_drag = gh_GUI.GH_DragInfo

# Define variables to store data
O = []
Cp = []
Wp = []
Ds = []
De = []

# Function to run when the component is executed
def run_component(B):
    global O, Cp, Wp, Ds, De

    # Get the current component position in the Grasshopper canvas space
    current_component_position = ghenv.Component.Attributes.Bounds.Location

    # Output the current component position in the O output
    O = current_component_position

    # Get the cursor position from Windows
    Wp = Forms.Cursor.Position

    # Get The X,Y Canvas Position Of The Mouse Cursor
    Cp = gh_canvas.CursorControlPosition
    print(Cp)

    # !!! How Can I Get The Drag Start Point And Drag End Point? !!!
    Pg = gh.GUI.GH_DragInfo.Point_Drag.GetValue
    Ds = gh.GUI.GH_DragInfo.Point_Start
    #De = gh_drag.Point_End

    return O, Cp, Wp, Ds, De

if __name__ == "__main__":
    if B:
        O, Cp, Wp, Ds, De = run_component(B)
        # You can use the result variable to access the values returned by the function

20230823_Add_Panel_On_Wire_Drag_01b.gh (9.9 KB)

EDIT:

Just found this! (Moving in this direction now)

1 Like

Making a little bit of progress… @Intuos

I’ve got the panel being added to the canvas to the right of the component.

Currently it’s connecting the panel output to the component input. I’m trying to reverse this order and connect the component output a to the panel input but can’t find the opposite method of Input.AddSource.

Once I get that working I’m going to work on Ouput[0] Drag Release to trigger the code to add the panel

C# Code Thus Far:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

public class Script_Instance : GH_ScriptInstance
{
  
  private void RunScript(bool Test_Toggle, object y, System.Collections.Generic.IEnumerable<object> Data, out object a)
  {
    if(Test_Toggle)
    {
        //Instantiate New Panel
        Grasshopper.Kernel.Special.GH_Panel test_panel = new Grasshopper.Kernel.Special.GH_Panel();
        test_panel.CreateAttributes(); //sets up default values, and makes sure your panel doesn't crash rhino

        //customize panel (position, etc)
        int outputcount = this.Component.Params.Input[1].SourceCount;
        test_panel.Attributes.Pivot = new PointF((float) this.Component.Attributes.DocObject.Attributes.Bounds.Right + 20, (float) this.Component.Params.Output[1].Attributes.Bounds.Y + outputcount * 120);
        //test_panel.;

        //Until now, the panel is a hypothetical object.
        // This command makes it 'real' and adds it to the canvas.
        GrasshopperDocument.AddObject(test_panel, false);

        //Connect the new panel to this component
        this.Component.Params.Input[1].AddSource(test_panel);
    }

    a = Data;
  }
}

Graph Space (Button Not Pressed):

Graph Space (After Button Pressed):

20230823_Add_Panel_On_Wire_Drag_01c.gh (5.5 KB)

2 Likes

Looks nice, I’ll keep my eye on the thread, sorry, can’t help you with the coding.

1 Like

You need to use AddSource on the Panel, providing it the output param of your current component (Params.Output[i])

1 Like

Thanks @magicteddy that makes sense! I’ll give it a try

Thanks again, that did the trick!

        //Connect Component Out To Panel In
        test_panel.AddSource(this.Component.Params.Output[1]);

Graph Space (After Button Press)

Onto the wire drag code!

EDIT:

Success getting the cursor position when the script is “called” and spawning a panel at the cusor location(currently with a button test)

What I am attempting to do now is set the cursor position when a wire is dragged off into blank canvas space. I believe this can be achieved with Mouse Down, Mouse Up but I’m having difficulty getting the event handlers working.

My thought is to leverage mouse down to trigger the event and when the mouse is realeased (mouse up) it will fire the function and spawn the connected panel at the released location in GH canvas space.

Current Code:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

public class Script_Instance : GH_ScriptInstance
{
    private void RunScript(bool Test_Toggle, IEnumerable<object> Data, out object D, out object CP)
    {
        // Get Cursor Position
        PointF cursor_pos = new PointF((float)Rhino.UI.MouseCursor.Location.X, (float)Rhino.UI.MouseCursor.Location.Y);

        // Use Test_Toggle As Fake Event To Test Logic In Function
        if (Test_Toggle)
        {
            // Get Test Event Location
            var bounds = Component.Params.Input[0].Sources[0].Attributes.Bounds;
            PointF test_event_location = new PointF(bounds.X + bounds.Width, bounds.Y - bounds.Height / 2);

            // Instantiate New Panel
            Grasshopper.Kernel.Special.GH_Panel test_panel = new Grasshopper.Kernel.Special.GH_Panel();
            test_panel.CreateAttributes(); // sets up default values and makes sure your panel doesn't crash Rhino

            // Customize Panel Attributes, etc.
            int outputcount = this.Component.Params.Output[0].SourceCount;

            // Set Panel Location To Cursor Position
            test_panel.Attributes.Pivot = test_event_location;

            // Add Panel To Document
            GrasshopperDocument.AddObject(test_panel, false);

            // Connect Component Out To Panel In
            test_panel.AddSource(this.Component.Params.Output[0]);
        }

        D = Data;
        CP = cursor_pos;
    }
}


        //Customize Panel Attributes, etc.
        int outputcount = this.Component.Params.Output[0].SourceCount;
        //Set Panel Location To Cursor Position
        test_panel.Attributes.Pivot = offsetCursorPos;

        //Add Panel To Document
        GrasshopperDocument.AddObject(test_panel, false);

        //Connect Component Out To Panel In
        test_panel.AddSource(this.Component.Params.Output[0]);

    }

    D = Data;
    CP = cursor_pos;
  }
}

And my poor attempt at the event handler logic for MouseDown/MouseUp:

public class Script_Instance : GH_ScriptInstance
{
    // Define a variable to hold the GH_Canvas
    private GH_Canvas canvas;

    // Subscribe to the DocumentObjectMouseDown event
    protected override void BeforeSolveInstance()
    {
        // Set the active canvas
        canvas = Grasshopper.Instances.ActiveCanvas;

        // Check if the canvas is not null before subscribing to the event
        if (canvas != null)
        {
            canvas.DocumentObjectMouseDown += Canvas_DocumentObjectMouseDown;
        }
    }

    // Unsubscribe from the event when done
    protected override void AfterSolveInstance()
    {
        if (canvas != null)
        {
            canvas.DocumentObjectMouseDown -= Canvas_DocumentObjectMouseDown;
        }
    }

    // Event handler for DocumentObjectMouseDown
    private void Canvas_DocumentObjectMouseDown(GH_Canvas sender, GH_Canvas.MouseEvent e)
    {
        // Check if the left mouse button is clicked
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            // Check if the user clicked on an output parameter
            if (e.SourceParameter != null && e.SourceParameter.Attributes.GetTopLevel.DocObject is IGH_Param outputParam)
            {
                // Your existing code for creating a panel can go here
                if (Test_Toggle)
                {
                    // Instantiate New Panel
                    Grasshopper.Kernel.Special.GH_Panel test_panel = new Grasshopper.Kernel.Special.GH_Panel();
                    test_panel.CreateAttributes(); // sets up default values and makes sure your panel doesn't crash Rhino

                    // Customize panel (position, etc)
                    int outputcount = this.Component.Params.Output[0].SourceCount;
                    test_panel.Attributes.Pivot = new PointF((float)this.Component.Attributes.DocObject.Attributes.Bounds.Right + 30, (float)this.Component.Params.Output[0].Attributes.Bounds.Y + 50);

                    // Until now, the panel is a hypothetical object.
                    // This command makes it 'real' and adds it to the canvas.
                    GrasshopperDocument.AddObject(test_panel, false);

                    // Connect Component Out To Panel In
                    test_panel.AddSource(this.Component.Params.Output[0]);
                }
            }
        }
    }

    private void RunScript(bool Test_Toggle, System.Collections.Generic.IEnumerable<object> Data, out object D, out object CP)
    {
        PointF cursor_pos = new PointF((float)Rhino.UI.MouseCursor.Location.X, (float)Rhino.UI.MouseCursor.Location.Y);

        D = Data;
        CP = cursor_pos;
    }
}

Any help is greatly beneficial and once this logic gets worked out theoretically we could use it to spawn any “favorite” panel on wire drag out which could speed up workflows, offering convenience as well.

Graph Space (Pre “Event”):

Graph Space (Post “Event”):

20230823_Add_Panel_On_Wire_Drag_01d.gh (10.3 KB)

This is the kind of functionality I am trying to achieve, spawning a connected panel from a wire drag out (Example from Unreal Engine Blueprint GUI):

In Unreal this action prompts a search field but for this exercise in GH I just am trying to spawn the panel every time only.

2 Likes

Do you really have to do the wire drag or would it be easier to implement a modifier key + click combination on one of the component outputs to spawn a panel?

1 Like

I went back in R7, C# editor is too buggy in RWIP.

Single click on any output instantiates a Panel.
I’m sure you’ll find out a cool way to change this to a wire release event :slight_smile:

testMouse.gh (2.3 KB)

5 Likes

Amazing! The logic to work on any component was where I was headed next. Thank you so much @magicteddy :pray:

It wouldn’t have to be the wire drag. I was thinking the nice thing about wire drag is it would allow you to control where the panel is placed easier. Instead of having it placed and then moving it out of the way of clashing components (2 steps) wire drag release could spawn the panel and panel position in one go.

Seeing @magicteddy s example below though I think I’ll try and add a toggle option so you can choose the single click or the wire drag method

Wouldn’t both be possible then? Modifier click for instant panels and dragging for precise panels?
I’m thinking if you need to check multiple lists/ trees, you can click spawn panels, select all of them and arrange them, which you would also need to do if you wire drag the panels out (to make sure the list indices align).

1 Like

Yes I believe so, the original version of the script supported this by checking the data count in the input, getting the panel bounds and when multiple or a new panel is added it would position it below the other panel. So if the data output had say 3 lists inside of it, it would spawn 3 non overlapping panels all wired to that output.

I moved away from that thinking 1 panel would still show the nested data tree structure from a single output but I see how it could be helpful to have the option for it

EDIT: GH2 handles this really elegantly in that it allows you to “merge” multiple panels side by side

I tried to do this a while ago but looking for the position of existing components is a nightmare.

I’ve made those as well but again, the placement is fixed depending on the insertion point. When I use those shortcut components, I just make sure I have enough room for the components to get instantiated.

I’m not sure the click alone is a good way to instantiate Panels, it’s too easy to click on a component by mistake. This was merely proof of concept. Maybe hover the mouse on the output and hit P on the keyboard ?

A better management of the event is probably possible if you create a custom component (not using C# Script) or a custom plugin that wouldn’t even require a component to be added to the canvas (like Sunglasses or SnappingGecko).
(I don’t know how to do the latter :sweat_smile:)

3 Likes

Hey that’s pretty cool! It’s kind of like spawning a user object cluster without the cluster wrapper. Interesting! There’s definitely times where I wish a “prefab” of nodes like that was available. Thanks for sharing!

Yes, heading the plugin route is eventually where this may go, though I need to learn that path as well :sweat_smile: x2

You can use Metahopper snippets to make custom node groups (without making clusters).

4 Likes

thanks for the tip!

You just ruined the proud I had coding those components :sob:.
It’s just perfect with MetaHopper !

Making progress but still for some reason the panel is spawned on “click” and not “leave”. Perhaps the flaw is that “leave” is always called after click anyways? so it’s prematurely spawning the panel? but it seems odd because if you check the Rhino command line it does show that cursor_pos is being updated correctly only on Mouse Leave

I tried to split the events into two separate events.

  1. Event where on mouse click, we get the Param info at that location
  2. Event where on mouse leave, we get the cursor_position at that time and spawn the panel there

20230823_Add_Panel_On_Wire_Drag_02a.gh (4.7 KB)