# Lags with mouse tracking on RhinoBeta

**URL:** <https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329>\
**Category:** Scripting\
**Tags:** mac, python, macgrasshopper, scripting\
**Created:** [September 2, 2026, 11:00am UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329 "2026-09-02T11:00:26Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![François\_Farnault](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/fran%C3%A7ois_farnault/32/619206_2.png) [@François\_Farnault](https://discourse.mcneel.com/u/Fran%C3%A7ois_Farnault)\
**Post date:** [September 2, 2026, 11:00am UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/1 "2026-09-02T11:00:26Z")

</div>

Hello,

I am developing a « Move Normal to Surface » definition including a Grasshopper Python script.

It is integrated in a larger definition aimed at facilitating jewelry design.

Thanks to [this post](https://discourse.mcneel.com/t/button-mouse-event-again/74625/14) and [this other post](https://discourse.mcneel.com/t/what-can-trigger-geometry-pipeline-component-refreshing/106497/8) I could make a definition running smoothly on Rhino 8.

```python
import Rhino
import Grasshopper
from ghpythonlib.componentbase import executingcomponent as component

class MyMouseCallback(Rhino.UI.MouseCallback):

    def __init__ (self):
        super(). __init__ ()
        self.move = None

    def OnMouseMove(self, arg : Rhino.UI.MouseCallbackEventArgs):
        self.move = arg
        ghenv.Component.OnPingDocument().ScheduleSolution(
            1,
            ghenv.Component.ExpireSolution(True)
        )
 
 
class MyComponent(component):

    def RunScript(self):
        return self.getFrustrumLine()

    def getFrustrumLine(self):
        if self.mouse.move:

            mousePoint = self.mouse.move.ViewportPoint
            frustrumLine = self.mouse.move.View.ActiveViewport.ClientToWorld(mousePoint)
            return frustrumLine

    def __enter__ (self):
        self.mouse = MyMouseCallback()
        self.mouse.Enabled = True

    def __exit__ (self):
        self.mouse.Enabled = False
        del self.mouse

```

[move\_normal\_to\_surface.gh](https://discourse.mcneel.com/uploads/short-url/mv8WG6J5eMqiVibq5FjkoFGcVUV.gh) (10.2 KB)

However I am experiencing a loss in fluidity when using the same definition on RhinoBeta : the cursor tracking moves slowly and lags.

[https://youtu.be/1yp0nBytNgk](https://youtu.be/1yp0nBytNgk)

I am using a MacBook Pro.

Could you please let me know why this issue is happening and if there is a way to solve it ?

---

<div class="post-metadata">

**Author:** ![dale](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/dale/32/685184_2.png) [@dale](https://discourse.mcneel.com/u/dale)\
**Post date:** [September 2, 2026, 3:01pm UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/2 "2026-09-02T15:01:27Z")

</div>

Hi @François_Farnault,

Does this work any better?

[move\_normal\_to\_surface.gh](https://discourse.mcneel.com/uploads/short-url/3ACAqFN3LO9pWygFGBRNIyUgbK.gh) (29.0 KB)

– Dale

---

<div class="post-metadata">

**Author:** ![François\_Farnault](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/fran%C3%A7ois_farnault/32/619206_2.png) [@François\_Farnault](https://discourse.mcneel.com/u/Fran%C3%A7ois_Farnault)\
**Post date:** [September 2, 2026, 10:49pm UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/3 "2026-09-02T22:49:43Z")

</div>

Hello @Dale\_Fugier

Yes It does work better. Thank you.

There is still a remnant lag that makes me wonder if there would be a better approach.

Also I did not yet managed to implement this solution in a script with different mouse events that can trigger solution expiration.

Today is the first day at school so I will work on this next weekend.

For context I am a teacher.

I might have this completely wrong but I think you added a flag to ensure that only one scheduling can happen during RunScript.

The confusing part to me is how and when this multiple scheduling could have happened.

You are giving me an opportunity to learn more about callbacks and document expiration.

Thank you again for your help.

![v1_dale_2](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/4/2/c/42c0100553b75b72ab8dc68e42491097c3b6a85f.gif)

---

<div class="post-metadata">

**Author:** ![François\_Farnault](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/fran%C3%A7ois_farnault/32/619206_2.png) [@François\_Farnault](https://discourse.mcneel.com/u/Fran%C3%A7ois_Farnault)\
**Post date:** [September 7, 2026, 6:19pm UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/4 "2026-09-07T18:19:15Z")

</div>

Hello, here is some progress.

I asked Claude to help clarify the sequence of events.

I had initially mistaken \ __enter_\_ and \__ exit_\_ for BeforeRunScript and AfterRunScript.

However, \ __enter_\_ and \__ exit_\_ are called once per component instance lifecycle (when the document is opened/closed, or when the component is added/deleted).

By contrast, BeforeRunScript and AfterRunScript are called on every solution.

We adapted @Dale\_Fugier ’s solution to a few other events, including recording a mouse click, which makes this kind of “bake on click” interaction possible:

![bake_on_click](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/0/1/0/010d7a2bfe7e6501a0df5eda3c50c46e6dc5f8b2.gif)

```python
import Rhino
import Grasshopper
from ghpythonlib.componentbase import executingcomponent as component

class MyMouseCallback(Rhino.UI.MouseCallback):

    def __init__ (self):
        super(). __init__ ()
        self.move = None
        self.click = None
        self.pending = False

    def OnMouseMove(self, arg):
        # Store the raw mouse event
        # This data is only read later, when Grasshopper actually solves.
        self.move = arg
        self._request_solution()

    def OnMouseUp(self, arg):
        # Only register left-click to avoid triggering on right-click view rotation.
        if arg.MouseButton == Rhino.UI.MouseButton.Left:
            self.click = True
            self._request_solution()

    def _request_solution(self):
        # Avoid piling up multiple solve requests: one pending at a time.
        if not self.pending:
            self.pending = True
            ghenv.Component.OnPingDocument().ScheduleSolution(5, self.solve)

    def solve(self, doc):
        self.pending = False
        ghenv.Component.ExpireSolution(False)

    def consume_click(self):
        # One-shot read: returns the click state then resets it to False.
        click = self.click
        self.click = False
        return click

class MyComponent(component):

    def RunScript(self):
        frustrum_line = self._getFrustrumLine()
        click = self.mouse.consume_click()
        return frustrum_line, click

    def _getFrustrumLine(self):
        if self.mouse.move:
            mousePoint = self.mouse.move.ViewportPoint
            return self.mouse.move.View.ActiveViewport.ClientToWorld(mousePoint)

    def __enter__ (self):
        self.mouse = MyMouseCallback()
        self.mouse.Enabled = True

    def __exit__ (self):
        self.mouse.Enabled = False
        del self.mouse

```

[move\_normal\_to\_surface\_bakeOnClick.gh](https://discourse.mcneel.com/uploads/short-url/fsNWdrs4Mnva7c4k0M6Kgq1xclZ.gh) (6.7 KB)

---

<div class="post-metadata">

**Author:** ![François\_Farnault](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/fran%C3%A7ois_farnault/32/619206_2.png) [@François\_Farnault](https://discourse.mcneel.com/u/Fran%C3%A7ois_Farnault)\
**Post date:** [September 7, 2026, 6:21pm UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/5 "2026-09-07T18:21:07Z")

</div>

We somehow managed to isolate the source of the lag by removing ScheduleSolution() from the script and using Redraw() and DrawViewportWires() instead.

```python
import Rhino
import Grasshopper
import scriptcontext
import System.Drawing
from ghpythonlib.componentbase import executingcomponent as component

class MyMouseCallback(Rhino.UI.MouseCallback):

    def __init__ (self):
        super(). __init__ ()
        self.move = None

    def OnMouseMove(self, arg):
        # Store the raw mouse event, then force an immediate redraw.
        # DrawViewportWires reads this directly on every redraw.
        self.move = arg
        scriptcontext.doc.Views.Redraw()

class MyComponent(component):

    def RunScript(self,
            targetSurface: Rhino.Geometry.Surface,
            objectToOrient: Rhino.Geometry.Brep):
        self.targetSurface = targetSurface
        self.objectToOrient = objectToOrient

    def _getHitFrame(self):
        if not self.mouse.move or self.targetSurface is None:
            return None

        mousePoint = self.mouse.move.ViewportPoint
        frustrumLine = self.mouse.move.View.ActiveViewport.ClientToWorld(mousePoint)

        t = Rhino.Geometry.Intersect.Intersection.CurveSurface(
            frustrumLine.ToNurbsCurve(), self.targetSurface, 0.001, 0.001
        )
        if t is None or t.Count == 0:
            return None

        closestHit = t[t.Count - 1]
        u, v = closestHit.SurfacePointParameter()
        success, frame = self.targetSurface.FrameAt(u, v)
        return frame if success else None

    def _getOrientedObject(self):
        hitFrame = self._getHitFrame()
        if hitFrame is None or self.objectToOrient is None:
            return None

        xform = Rhino.Geometry.Transform.PlaneToPlane(Rhino.Geometry.Plane.WorldXY, hitFrame)
        oriented = self.objectToOrient.Duplicate()
        oriented.Transform(xform)
        return oriented

    def DrawViewportWires(self, arg):
        orientedObject = self._getOrientedObject()
        if orientedObject is not None:
            arg.Display.DrawBrepShaded(orientedObject, self.material)

    def __enter__ (self):
        self.mouse = MyMouseCallback()
        self.mouse.Enabled = True
        self.material = Rhino.Display.DisplayMaterial(System.Drawing.Color.Blue)

    def __exit__ (self):
        self.mouse.Enabled = False
        del self.mouse

```

Something interesting happens:

- Objects drawn with DrawViewportWires() do not lag.
- Objects drawn through the component preview after ScheduleSolution() do lag.

![lag](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/f/1/8/f189fe46d6db8e20a4931387eb6f4fffee4fceeb.gif)

In both cases, a MouseCallback is used, so I don’t think the issue comes from the mouse callback itself. This example seems to points at the solve mechanism itself as the source of the lag on RhinoBETA.

[move\_normal\_to\_surface\_DrawViewportWires.gh](https://discourse.mcneel.com/uploads/short-url/qVVlZzcIFlR02B4t01X4DfOsFDk.gh) (13.2 KB)

---

<div class="post-metadata">

**Author:** ![François\_Farnault](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/fran%C3%A7ois_farnault/32/619206_2.png) [@François\_Farnault](https://discourse.mcneel.com/u/Fran%C3%A7ois_Farnault)\
**Post date:** [September 7, 2026, 6:22pm UTC](https://discourse.mcneel.com/t/lags-with-mouse-tracking-on-rhinobeta/222329/6 "2026-09-07T18:22:09Z")

</div>

I have tried this on Rhino8 Mac and the lag is barely perceptible.

The lag is still present in RhinoBeta, however, and I am concerned that it could lead to an unpleasant user experience.

My aim is to use this definition as a teaching example for students.

I could route the interactive logic through DrawViewportWires(), but my students primarily work by connecting native Grasshopper components rather than writing Python code.

For an optimal teaching experience, I would like to keep the Python scripting to a minimum.

Is this a known limitation in RhinoBETA that might be addressed?

Or is there another recommended approach for achieving smooth live interactivity while relying mostly on native Grasshopper components?
