Cross Platform - Simple Mouse Event?

Hello,

I’m trying to implement the simple functionality of detecting a mouse button press and release and pulling my hair out. It seems Rhino.UI.MouseCallback has all the functionality I need for this but I can’t seem to get any code working that does anything “OnMouseDown” or “OnMouseUp” in Rhino8.

I’m sure I’m overlooking something obvious, I guess I’ve had my head in the sand for too long on this and now I’m just completely stuck. I’ve had success with mouse events in GH components I’ve written but just stumped in Rhino right now.

I found this code circa 2018 that seemed like a simple enough python implementation but only the else statement of the definition “TestSampleMouseCallback” prints its statement. (And quite honestly, I may just be misunderstanding the intent of this code but I expect it to print the button name on a Mouse click but that doesn’t seem to happen)

import System
import Rhino
import scriptcontext as sc

class SampleMouseCallback(Rhino.UI.MouseCallback):
  def OnMouseDown(self, e):

    print "OnMouseDown", e.Button
    
def TestSampleMouseCallback():
    
    if sc.sticky.has_key('TestSampleMouseCallback'):
        callback = sc.sticky['TestSampleMouseCallback']
        if callback:
            callback.Enabled = False
            callback = None
            sc.sticky.Remove('TestSampleMouseCallback')
    else:
        callback = SampleMouseCallback()
        callback.Enabled = True
        sc.sticky['TestSampleMouseCallback'] = callback
        Rhino.RhinoApp.WriteLine("Click somewhere...")

if __name__ == "__main__":
  TestSampleMouseCallback()

I’m after having a cross platform code solution to simply test if a mouse button was pressed or released and then DoSomething().

Any leads are greatly appreciated, thank you!

Hi.

This works in 7:

  private void RunScript(bool On, ref object A)
  {
    if(On){
      comp = this.Component;
      if(md == null){md = new MyMouseDownClass();}
      md.Enabled = true;
    }else{
      if(md != null){md.Enabled = false;}
    }
    A = counter;
  }

  // <Custom additional code> 
  public MyMouseDownClass md;
  public static IGH_Component comp;
  public static int counter;
  public class MyMouseDownClass : Rhino.UI.MouseCallback{
    public MyMouseDownClass(){}
    protected override void OnMouseDown(Rhino.UI.MouseCallbackEventArgs e){
      // Your stuff here
      counter++;
      comp.ExpireSolution(true);
    }
  }

2024-02-26 23_26_05-Window
mouse down event.gh (3.8 KB)


probably there are also some System methods, this one from Rhino.UI detect only events inside viewports and have a bit of delay detecting fast clicks… (maybe to detect double-clicks…?)

Hi @michaelvollrath,

looks like in line 6 there is an indentation error, just add 2 spaces before the def and it runs:

grafik

_
c.

1 Like

Thanks @maje90, that does work for me in Grasshopper, thank you! However, I’m trying to adapt the code to be run independently of Grasshopper (within Rhino only).

Thanks @clement, that was a copy paste error but good catch thank you!

Moving forward now, I think that did it. I’ll report back shortly

EDIT:

Okay so I couldn’t get it working with Rhino.UI and am using Windows.Forms for now to register the mouse callback.

Any ideas on a crossplatform solution for that? Can I use Eto for mouse events in this case?

import System
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

class MiddleMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        if e.Button == System.Windows.Forms.MouseButtons.Middle:
            Rhino.RhinoApp.WriteLine("Middle mouse down")
        else:
            Rhino.RhinoApp.WriteLine("Other mouse down")
    
    def OnMouseUp(self, e):
        if e.Button == System.Windows.Forms.MouseButtons.Middle:
            Rhino.RhinoApp.WriteLine("Middle mouse up")
        else:
            Rhino.RhinoApp.WriteLine("Other mouse up")
    
def TestSampleMouseCallback():
    
    if sc.sticky.has_key('TestSampleMouseCallback'):
        callback = sc.sticky['TestSampleMouseCallback']
        if callback:
            callback.Enabled = False
            callback = None
            sc.sticky.Remove('TestSampleMouseCallback')
    else:
        callback = MiddleMouseCallback()
        callback.Enabled = True
        sc.sticky['TestSampleMouseCallback'] = callback
        Rhino.RhinoApp.WriteLine("Click somewhere...")

if __name__ == "__main__":
    TestSampleMouseCallback()

EDIT:

Okay I think I figured it out via Rhino.UI instead of System.Windows.Forms

updated code:

import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

class MiddleMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        if e.Button == Rhino.UI.MouseButton.Middle:
            Rhino.RhinoApp.WriteLine("Middle mouse down")
        else:
            Rhino.RhinoApp.WriteLine("Other mouse down")
    
    def OnMouseUp(self, e):
        if e.Button == Rhino.UI.MouseButton.Middle:
            Rhino.RhinoApp.WriteLine("Middle mouse up")
        else:
            Rhino.RhinoApp.WriteLine("Other mouse up")
    
def TestSampleMouseCallback():
    
    if sc.sticky.has_key('TestSampleMouseCallback'):
        callback = sc.sticky['TestSampleMouseCallback']
        if callback:
            callback.Enabled = False
            callback = None
            sc.sticky.Remove('TestSampleMouseCallback')
    else:
        callback = MiddleMouseCallback()
        callback.Enabled = True
        sc.sticky['TestSampleMouseCallback'] = callback
        Rhino.RhinoApp.WriteLine("Click somewhere...")

if __name__ == "__main__":
    TestSampleMouseCallback()

EDIT 2:

Okay I feel silly, I was way over thinking it because I was trying to use print statements in my definitions that weren’t showing up where as Rhino.RhinoApp.WriteLine works.

So I wrongfully assumed nothing was happening because I didn’t know that print statements could “not print” haha

Working Code:

import System
import Rhino
import scriptcontext as sc

class SampleMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        Rhino.RhinoApp.WriteLine("mousy")
        print "OnMouseDown", e.Button
    
def TestSampleMouseCallback():
    
    if sc.sticky.has_key('TestSampleMouseCallback'):
        callback = sc.sticky['TestSampleMouseCallback']
        if callback:
            callback.Enabled = False
            callback = None
            sc.sticky.Remove('TestSampleMouseCallback')
    else:
        callback = SampleMouseCallback()
        callback.Enabled = True
        sc.sticky['TestSampleMouseCallback'] = callback
        Rhino.RhinoApp.WriteLine("Click somewhere...")

if __name__ == "__main__":
  TestSampleMouseCallback()
2 Likes

Hi @michaelvollrath, caution with events. It could be that you’re not seeing what really fires if a script errors out and the event is still active from a previous run and the one in sticky is not the one you’re actually accessing.

print actually works in your case, but below doesn`t:

if e.Button == Rhino.UI.MouseButton.Middle:

You might replace it with below to get middle mouse button:

if e.Button == e.Button.Middle:
    print "MiddleMouseDown"

_
c.

1 Like

Interesting, thank you @clement , that makes sense about the sticky in relation to events and I’ll give the line you shared a go. Much appreciated!

EDIT:

Unfortunately, this bit of code doesn’t seem to work for me as it prints/returns nothing it seems

if e.Button == e.Button.Middle:
    print "MiddleMouseDown"

Do I need to restart Rhino when modifying code pertaining to events or clear the sticky or something else?

Perhaps I shouldn’t be using the script editor?

Thanks for any suggestions!

1 Like

@michaelvollrath, sorry for the late answer. I did not get a notification for your reply, probably because the thread has been marked as solved ?!

You do not need to restart if you make changes to your script ONLY while the event is disabled. If you get an error from your script while the event is enabled or called, it could happen that you’re ending up with multiple events running. Then you must restart Rhino to get rid of the event(s), unless you can get hold of them through the sticky to disable them.

The whole purpose of adding an event to the sticky is that you need to get hold of it to disable it. If you would enable an event multiple times and store it into the same sticky (using the same key), you can only disable the last one added to the sticky but the events would stack up (and they would be fired multiple times).

If you create code for your event eg. in the OnMouseDown function, it could happen that Rhino does not print errors even when there are some. Example:

class SampleMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        print "OnMouseDown: {}".format(e.Button)
        a = b

The variable b has been defined nowhere, but you get no error. It seems, Rhino suppresses errors in delegates. If multiple errors occur in delegates and are not handled, it could happen that Rhino crashes at some point.

To prevent this, it is a good practice to put the whole function code into try except blocks and print exceptions yourself. Example:

class SampleMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        try:
            print "OnMouseDown: {}".format(e.Button)
            a = b
        except Exception as ex:
            print ex

Once you click with LMB, you’ll get this printed:

OnMouseDown: Left
global name ‘b’ is not defined

You might make the delegate disable itself in case of an error using:

try:
    # your code
except Exception as ex:
    print ex
    self.Enabled = False

so you don’t end up with multiple events after an error occured or when you forgot to disable it before making changes. I often do this when scripting display conduits so it stops drawing in case of an error.

Are you trying this on Mac or Win ? Note that the mouse events only are fired when clicking in a viewport. I do get below after a fresh restart. (clicked in this order: LMB, MMB, RMB):

OnMouseDownTest

the e.Cancel = True in this example prevents that Rhino acts on my clicks, eg. on RMB, it usually would repeat the last command or on MMB it usually would popup the MMB toolbar.

does this help ?

_
c.

1 Like

Thanks @clement this is really helpful! I’ll implement your suggestions and report back. Thank you for the thorough break down!

1 Like