I want to use the scroll wheel to iterate through a list of objects in Grasshopper. I’m currently doing this with a text dot in Rhino. Once it is selected, I can change the height input by clicking the text field and scrolling the mouse wheel. The textdot in the Rhino viewport updates instantly and is synchronized in Grasshopper after a short delay. The problem with this approach is that the smallest size is 3 units and I need index 0…
I also have a script to change the value of a textdot with an Eto dialog / popup.
It uses a numeric stepper and the value can be changed with the scroll wheel.
The problem with the script is that it only updates the textdot after clicking ok.
Is there a way to get the new value to a textdot in realtime without having to press ok?
################################################################################
# Source file: SampleEtoRebuildCurve.py
# MIT License - Copyright (c) 2017 Robert McNeel & Associates.
# See License.md in the root of this repository for details.
################################################################################
# Imports from
from Rhino.UI import *
from Eto.Forms import *
from Eto.Drawing import *
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs
rs.Command("_-Gumball Hide Enter")
################################################################################
# Class to hold arguments
################################################################################
class TextDotArgs():
# Initializer
def __init__(self):
self.Percentage = 10
# Validator
def IsValid(self):
if self.Percentage < 0 or self.Percentage > 30: return False
return True
################################################################################
# Change TextDot value dialog class
################################################################################
class TextDotDialog(Dialog[bool]):
# Initializer
def __init__(self, args):
self.Args = args
# Initialize dialog box
self.Title = 'SetTextDot'
self.Padding = Padding(5)
# Create layout
layout = TableLayout()
layout.Padding = Padding(5)
layout.Spacing = Size(5, 5)
layout.Rows.Add(self.CreateSteppers())
layout.Rows.Add(None) # spacer
layout.Rows.Add(None) # spacer
layout.Rows.Add(self.CreateButtons())
# Set the dialog content
self.Content = layout
# Creates numeric stepper controls
def CreateSteppers(self):
# Create labels
label0 = Label(Text = 'New Value:')
label2 = Label(Text = '({})'.format(self.Args.Percentage))
# Create numeric steppers
self.Percentage = NumericStepper(
Value = self.Args.Percentage,
MinValue = 0,
MaxValue = 30
)
# Create table layout
layout = TableLayout()
layout.Spacing = Size(5, 5)
layout.Rows.Add(TableRow(label0, self.Percentage))
return layout
# OK button click handler
def OnOkButtonClick(self, sender, e):
# Harvest control values before closing
self.Args.Percentage = self.Percentage.Value
self.Close(True)
# Cancel button click handler
def OnCancelButtonClick(self, sender, e):
self.Close(False)
# Create button controls
def CreateButtons(self):
# Create the default button
self.DefaultButton = Button(Text = 'OK')
self.DefaultButton.Click += self.OnOkButtonClick
# Create the abort button
self.AbortButton = Button(Text = 'Cancel')
self.AbortButton.Click += self.OnCancelButtonClick
# Create button layout
button_layout = TableLayout()
button_layout.Spacing = Size(5, 5)
button_layout.Rows.Add(TableRow(None, self.DefaultButton, self.AbortButton, None))
return button_layout
################################################################################
# Function to change the TextDot value
################################################################################
def TestTextDot():
res, objref = Rhino.Input.RhinoGet.GetMultipleObjects(
"Select at least one TextDot",
True,
Rhino.DocObjects.ObjectType.TextDot
)
if res != Rhino.Commands.Result.Success: return
args = TextDotArgs()
dlg = TextDotDialog(args)
rc = dlg.ShowModal(RhinoEtoApp.MainWindow)
if rc and args.IsValid():
objects = sc.doc.Objects.GetSelectedObjects(False, False)
for obj in objects:
if isinstance(obj, Rhino.DocObjects.TextDotObject):
obj.Geometry.Text = str(int(args.Percentage))
obj.CommitChanges()
sc.doc.Views.Redraw()
################################################################################
# Check to see if this file is being executed as the "main" python
# script instead of being used as a module by some other python script
# This allows us to use the module which ever way we want.
################################################################################
if __name__ == "__main__":
TestTextDot()
I added this but it doesn’t seem to do anything:
def PercentageChanged(self, sender, e):
self.Args.Percentage = self.Percentage.Value
curtisw
(Curtis Wensley)
March 26, 2024, 3:28pm
3
Hi @martinsiegrist ,
You need to use the NumericStepper.ValueChanged
event to apply any changes the user has made when it happens.
1 Like
Sorry, where does this need to go in my script above?
nathanletwory
(Nathan 'jesterKing' Letwory)
March 27, 2024, 4:56am
5
After you’ve created your stepper component and with the PercentageChanged method in the same class:
self.Percentage.ValueChanged += self.PercentageChanged
Maybe rename PerccentageChanged
to OnPercentageValueChanged
to keep in line with the other event handler method names.
I’m doing something wrong.
def CreateSteppers(self):
# Create labels
label0 = Label(Text = 'New Value:')
label2 = Label(Text = '({})'.format(self.Args.Percentage))
# Create numeric steppers
self.Percentage = NumericStepper(
Value = self.Args.Percentage,
MinValue = 0,
MaxValue = 30
)
self.Percentage.Value = self.Args.Percentage
self.Percentage.ValueChanged += self.OnPercentageChanged
self.Percentage.Value = self.Args.Percentage
NumericStepper.ValueChanged += self.OnPercentageChanged
# Create table layout
layout = TableLayout()
layout.Spacing = Size(5, 5)
layout.Rows.Add(TableRow(label0, self.Percentage))
return layout
def OnPercentageChanged(self, sender, e):
self.Args.Percentage = NumericStepper.ValueChanged
# OK button click handler
def OnOkButtonClick(self, sender, e):
# Harvest control values before closing
self.Args.Percentage = self.Percentage.Value
self.Close(True)
Yeah, you are assigning the event to the value field instead of the value. Try:
def OnPercentageChanged(self, sender, e):
self.Args.Percentage = self.Percentage.Value
Also, I suggest renaming self.Percentage
to self.Stepper
or similar for readability.
1 Like
This is what I have and I’m lost:
def CreateSteppers(self):
# Create labels
label0 = Label(Text = 'New Value:')
label2 = Label(Text = '({})'.format(self.Args.Percentage))
# Create numeric steppers
self.Percentage = NumericStepper(
Value = self.Args.Percentage,
MinValue = 0,
MaxValue = 30
)
self.Percentage.Value = self.Args.Percentage
self.Percentage.ValueChanged += self.OnPercentageStepperChanged
# Create table layout
layout = TableLayout()
layout.Spacing = Size(5, 5)
layout.Rows.Add(TableRow(label0, self.Percentage))
return layout
def OnPercentageStepperChanged(self, sender, e):
self.Args.Percentage = self.Percentage.Value
The following two lines are for the event handler, right?
self.Percentage.Value = self.Args.Percentage
self.Percentage.ValueChanged += self.OnPercentageStepperChanged
nathanletwory
(Nathan 'jesterKing' Letwory)
March 27, 2024, 10:42am
10
Only the second line is for the event handler. The first line initializes the stepper value to whatever the TextDotArgs instance passed in has.
If you are expecting the label text to change, just updating the self.Args.Percentage
isn’t enough, since the label itself doesn’t have any reference to that variable.
You need to update the Text field of the Label object, and possibly call OnTextChanged()
(not sure if this is necessary, don’t use Eto much).
def OnPercentageStepperChanged(self, sender, e):
self.Args.Percentage = self.Percentage.Value
self.label2.Text = '({})'.format(self.Args.Percentage))
self.label2.OnTextChanged() # maybe not necessary?
Sorry I still don’t understand where the NumericStepper.ValueChanged
needs to be?
nathanletwory
(Nathan 'jesterKing' Letwory)
March 27, 2024, 11:28am
13
You already have it with the Percentage.ValueChanged
. You don’t need to add a line that says NumericStepper.ValueChanged += self.OnPercentageChanged
. Your self.Percentage
is the NumericStepper
.
Ok but still it only changes the text dot after clicking ok.
I want the change to happen instantly after scrolling or using the up down arrows on the text field.
nathanletwory
(Nathan 'jesterKing' Letwory)
March 27, 2024, 1:30pm
15
That is because you update the text dot only after closing the dialog.
The part that says:
for obj in objects:
if isinstance(obj, Rhino.DocObjects.TextDotObject):
obj.Geometry.Text = str(int(args.Percentage))
obj.CommitChanges()
This shows the eto form.
rc = dlg.ShowModal(RhinoEtoApp.MainWindow)
I really don’t understand what I have to change?
nathanletwory
(Nathan 'jesterKing' Letwory)
March 27, 2024, 2:43pm
17
Yes, that is where you show the modal form, but only after it returns do you do the update to the text dot geometry to set the new text.
You could pass in the ObjRef list you get from Rhino.Input.RhinoGet.GetMultipleObjects
to your TextDotArgs
instance.
Then whenever you handle the stepper ValueChanged
event you can loop over the list of ObjRef
, get the TextDot from it, duplicate the TextDot instance, set the new Text for the copy and use Replace(ObjRefInstance, TextDotInstance)
on the document Objects table.
I would pass on the document to your TextDotArgs as well, so you don’t have to use sc.doc
in the implementation of the update function.
1 Like
Ok, one thing is done
I was expecting that when I reference the textdot in Grasshopper, the value I get in Grasshopper would also update in realtime but that’s not the case unfortunately. It works when I’m using a trigger component on the annotation dot reference.
It sort of works with an event handler which triggers when something is selected:
from Rhino.UI import *
from Eto.Forms import *
from Eto.Drawing import *
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs
obj = rs.GetObject("select TextDot", filter = 8192, preselect = True, )
rs.Command("_-Gumball Hide Enter")
current_value = int(rs.TextDotText(obj))
print(current_value)
################################################################################
# Class to hold arguments
################################################################################
class TextDotArgs():
# Initializer
def __init__(self):
self.Percentage = current_value
# Validator
def IsValid(self):
if self.Percentage < 0 or self.Percentage > 30: return False
return True
################################################################################
# Change TextDot value dialog class
################################################################################
class TextDotDialog(Dialog[bool]):
# Initializer
def __init__(self, args):
self.Args = args
# Initialize dialog box
self.Title = 'SetTextDot'
self.Padding = Padding(5)
# Create layout
layout = TableLayout()
layout.Padding = Padding(5)
layout.Spacing = Size(5, 5)
layout.Rows.Add(self.CreateSteppers())
layout.Rows.Add(None) # spacer
layout.Rows.Add(None) # spacer
layout.Rows.Add(self.CreateButtons())
# Set the dialog content
self.Content = layout
# Creates numeric stepper controls
def CreateSteppers(self):
# Create labels
label0 = Label(Text = 'New Value:')
label2 = Label(Text = '({})'.format(self.Args.Percentage))
# Create numeric steppers
self.Percentage = NumericStepper(
Value = self.Args.Percentage,
MinValue = 0,
MaxValue = 30
)
self.Percentage.ValueChanged += self.OnNumericStepperChanged
# Create table layout
layout = TableLayout()
layout.Spacing = Size(5, 5)
layout.Rows.Add(TableRow(label0, self.Percentage))
return layout
def OnNumericStepperChanged(self, sender, e):
self.Args.Percentage = sender.Value
#print(int(self.Args.Percentage))
rs.TextDotText(obj, str(int(args.Percentage)))
# OK button click handler
def OnOkButtonClick(self, sender, e):
# Harvest control values before closing
self.Args.Percentage = self.Percentage.Value
self.Close(True)
# Cancel button click handler
def OnCancelButtonClick(self, sender, e):
self.Close(False)
# Create button controls
def CreateButtons(self):
# Create the default button
self.DefaultButton = Button(Text = 'OK')
self.DefaultButton.Click += self.OnOkButtonClick
# Create the abort button
self.AbortButton = Button(Text = 'Cancel')
self.AbortButton.Click += self.OnCancelButtonClick
# Create button layout
button_layout = TableLayout()
button_layout.Spacing = Size(5, 5)
button_layout.Rows.Add(TableRow(None, self.DefaultButton, self.AbortButton, None))
return button_layout
args = TextDotArgs()
dlg = TextDotDialog(args)
rc = dlg.ShowModal(RhinoEtoApp.MainWindow)
2 Likes
This is cool! In the example video above are you using the trigger or the event handler method?
Hey Michael, I’m using the event handler.
1 Like