Hi, there I was wondering if someone can guide me a bit on how to script an internal timer on the GH C# Component, I remember there was already a discussion in the old forum about it but I couldn’t found it, I was able to find class implementation for visual studio by @Dani_Abalde but I couldn’t make it work for C# Component. Thanks.
public class TimerTestComponent : GH_Component
{
Timer timer;
int counter;
int maxCounter;
int interval;
bool reset, run;
public override Guid ComponentGuid => new Guid("b53738ec-bda2-48bf-bf23-6894ecfd485f");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddBooleanParameter("Run", "Run", "", GH_ParamAccess.item, false);
pManager.AddBooleanParameter("Reset", "Rst", "", GH_ParamAccess.item, false);
pManager.AddIntegerParameter("Max", "Max", "", GH_ParamAccess.item, 0);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddIntegerParameter("Counter", "C", "", GH_ParamAccess.item);
}
public TimerTestComponent()
: base("TimerTest", "TimeT",
"Used to test time-based components",
"User", "Debug")
{
}
protected override void SolveInstance(IGH_DataAccess DA)
{
//I would not leave the SolveInstance without making sure that the timer is stopped... but well
if (!DA.GetData(0, ref run)) return;
if (!DA.GetData(1, ref reset)) return;
if (!DA.GetData(2, ref maxCounter)) return;
if (timer == null) {
timer = new Timer();
timer.Interval = 1000;
timer.Tick += UpdateSolution;
}
if (reset) Reset();
if (run && !timer.Enabled)
{
Start();
}
else if (!run || timer.Enabled && maxCounter != 0 && counter >= maxCounter)
{
Stop();
}
DA.SetData(0, counter);
}
public void Start()
{
timer.Start();
}
public void Stop()
{
timer.Stop();
}
public void Reset()
{
counter = 0;
}
public void Update()
{
// DoSomethingEpic
counter++;
}
public void UpdateSolution(object source, EventArgs e)
{
Update();
ExpireSolution(true);
}
}
Hi, Thanks for replaying, I change the UpdateSolution() and the timer.tick line and it seems to work but its behaving a little bit strange, so when run is false the value keeps increasing, So GH UI stops “working” not completely but…So Any Ideas???
TimerCounter.gh (5.9 KB)
Im pretty sure @LongNguyen explains it in one of these videos http://icd.uni-stuttgart.de/?p=22773
Im not in front of a computer, but from memory you can use
’ this.ExpireSolution(True)
If you declare your variables outside the solveInstance function the data will persist between iterations
It’s actually Component.ExpireSolution(true);
See attached user component for full example
Persistant data counter.ghuser (2.2 KB)
Nice, Thanks, I knew about ExpireSolution(), but in the example of Dani it was not clear to me why the need for EventArgs in the UpdateSolution(), so I was quite confuse. I Search on the Nguyen tutorial and yes I remember he was ask about it but I just couldn’t find that section…
Thanks again for all the help!!!
If anyone else is interested I also found a bit of a hack way to do this in python (component attached)
I haven’t tested performance yet with anything other than an integer counter
import rhinoscriptsyntax as rs
import scriptcontext as sc
#never know what might already be in sticky. This should avoid conflicts
unique_counter_key = "counter_"+str(ghenv.Component.InstanceGuid)
#retieve value from last loop
if sc.sticky.has_key(unique_counter_key):
i = (sc.sticky[unique_counter_key])
#otherwase create it
else:
i = 0
sc.sticky[unique_counter_key] = i
if run:
#----------------------------START
i += 1 #do something here
#----------------------------END
#force recalculation
ghenv.Component.ExpireSolution(True)
if reset:
i = 0
sc.sticky[unique_counter_key] = i
#save value for next loop
sc.sticky[unique_counter_key] = i
Hi, I was wondering if you know why in this example the Z value of the CameraLocation keeps increasing ? I been having this problem for while, and thought that by translating the script from python to c# might solved it but didn’t. This is a problem because at some point the value get so high that the viewport camara get lost. Thanks
Camara Increase.gh (6.8 KB)
You wanted to use a timer inside the component , the way to implement it is to subscribe to its Tick event and for that you need a delegate of the type HandlerMethod(object, EventArgs). Every time the timer makes a tick, the UpdateSolution() method is executed. Where to put the user code is in Update().
I thought you wanted it in a compiled component, not in a scripting component, my fault. When you pass it to a scripting component you haven’t passed it correctly, but well, it’s already solved.
Anyway, it’s not a good idea to expire the component directly, without leaving a delay or something, because in case the document takes a long time to compute/respond, it’s hard to stop it. In case of not using a timer (which is not necessary for this) it is more convenient to schedule a new solution with GH_Document.ScheduleSolution().
private void RunScript(bool reset, bool run, ref object A)
{
if(reset)
counter = 0;
if(run)
GrasshopperDocument.ScheduleSolution(100, d => {
//Press ctrl to stop it before it breaks.
if ((Control.ModifierKeys & Keys.Control) == Keys.Control) return;
this.Component.ExpireSolution(false);
counter++;
});
A = counter;
}
// <Custom additional code>
public int counter = 0;
// </Custom additional code>
Lately I been having some conflicts when using the ExpireSolution() along side other components that also expire the Solution(like Kangaroo…), I was going to use the timer for updating the value ActiveViewport.Bounds and the position of the mouse, but because using timers requires to Expire the solution, I was wondering if you know how to make use of the mouse events inside C# Scripting component, for only Expiring when a mouse event gets activated.
This is not exactly correct. You expire the object so that in the next solution the expired object is recomputed. You also need to force a new solution, either with ExpireSolution(true) or with GH_Document.ScheduleSolution() or any other way. The thing is, you don’t necessarily need the timer.
I find this strange, what are you trying to do?
The correct way to follow the native GH flow is to create custom attributes in a compiled component (with Visual Studio) and overwrite mouse interaction methods. Other valid but more complex options, which you don’t necessarily need to compile the code, and you can overwrite the interaction methods, are to create a Grasshopper.GUI.Canvas.Interaction.GH_AbstractInteraction and assign it in GH_Canvas.ActiveInteraction when appropriate, or create a Grasshopper.GUI.Widgets.GH_Widget. It can be done from a scripting component, but I do not recommend it if you are starting to code. Besides, they don’t look like solutions designed for this kind of thing either.
The bad way is to subscribe the mouse events of the Canvas, as it is a System.Windows.Forms.Control, but this comes out of GH’s interaction protocol, in addition to producing other undesirable problems if it is not implemented and used correctly. Nor do I recommend it if you are not familiar with OOP events.
So, what I’m trying to do is to create a grid rendered over the screen(like a hud), that takes the bounds of the ActiveViewport and updates it. ( constantly or by the mouse wheel event)
For then, determining the position of the four corners of the viewport and then assigning those values to the “+Infinity Axis” in the image. Also to render the mouse values on the screen.
The behavior the grid should have is very similar to one implemented on Google Earth, it should change the Grid Coordinates Values as the camera moves on the Z Axis.
It’s “simple” but I keep running with the problem that some of the properties of the viewport camera when update with the ExpireSolution(), tend to inflated very rapidly, and at some point the camera get lost, and the only way to refresh the viewport is by shutting down Rhino.
Example Problem.gh (5.4 KB)
So following the advice of Giulio, I’m trying to script it without using the ExpireSolution() or by reducing its use as much as possible.
I don’t think I can help you, I don’t understand why you need to expire the GH document or why update the viewport bounds. Rhino has its own mouse interaction and its own way of redrawing itself, nothing to do with GH. You also don’t specify which coordinates you mean by viewport corners or mouse position, are they screen coordinates? relative to a parent control? I really don’t understand anything xd
Where does it put you +infinite, do you want to put the Z height of the camera? I can only get to that, but in that case, assuming you stay in Cartesian space, is to take the height of the camera as the central value and calculate the rest of the Z values using trigonometry.
I don’t really need help with the coordinates issue , the updating of the viewport bounds(doc.Views.ActiveView.ActiveViewport.Bounds) has to do with the fact that when you change the monitor or resize the Rhino windows, the viewport bounds change so, if I draw the grid with no updating the grid wouldn’t align to the viewport.
The “+infinite” values are derive from the position of the viewport corners give by doc.Views.ActiveView.ActiveViewport.GetNearRect())… But this are details that don’t really matter that much.
Sorry for the messy explanation, Thanks for all the help!!
Check here more about…C# Timer
Hello @dharman,
I don’t know Python well enough to understand why your counter loops? And I don’t see any provision for setting an interval? It seems to loop as fast as possible?
So I made a couple of changes to your code and added a conventional Timer with settable interval to trigger the Python counter, like this:
import rhinoscriptsyntax as rs
import scriptcontext as sc
"""
Setup unique name to avoid conflicts with
other instances of this component, or anything else that may use sticky
sticky is a global dictionary
"""
unique_counter_key = "counter_"+str(ghenv.Component.InstanceGuid)
#retieve value from last loop
if sc.sticky.has_key(unique_counter_key):
i = (sc.sticky[unique_counter_key])
#otherwase create it
else:
i = 0
sc.sticky[unique_counter_key] = i
ghenv.Component.Message = str(i)
ghenv.Component.NickName = str(i)
i += 1 #do something here
if reset:
i = 0
#save value for next loop
sc.sticky[unique_counter_key] = i
counter_2019Dec27a.gh (5.8 KB)
Do you see any problems with this approach?
You can get rid of sticky
and the external timer component using the method demonstrated here:
Thank you. The code you refer to using ‘Component.ExpireSolution()’ looks similar to code posted in this thread above, including the bit from @dharman that I modified. The problem I have with it is that there’s no way to specify a delay interval?
It’s nice to be able to start/stop the timer without double-clicking the Timer component but looping as fast as possible doesn’t suite my needs. The changes I made to use a “sticky” counter make the Python only a counter, not a timer.
I found a different version of the same idea by @TomTom here, in C#, but unfortunately it doesn’t have a ‘Reset’ input:
A Simple Counter component that is absurdly simple indeed, if you ignore all the hidden overhead:
public class Script_Instance : GH_ScriptInstance
{
#region Utility functions
#region Members
/**/
private void RunScript(ref object A)
{
_counter++;
A = _counter;
}
// <Custom additional code>
int _counter = 0;
// </Custom additional code>
}
What got me started on this was a piece of code someone posted recently that used a Timer to trigger a Data Recorder that fed a Mass Addition component to get a count value!!! Completely ABSURD, of course, but when I looked for alternatives, it’s surprisingly difficult to find a GH “best practice”.for using a timer/counter?
Can you speculate on which method, using a specified interval, has the least computational overhead? Thank you.
I made a comment on line 15 where one can set the update interval (I just hardcoded it back then for simplicity). Attached a version that exposes this as an input parameter and also slightly changes the logic to have a Run
state boolean toggle and an explicit Reset
button:
191228_GHPython_Timer_00.gh (4.5 KB)
That certainly sounds pretty convoluted. Between the if "foo" not in globals()
approach to creating persistent variables and the updateComponent()
function, you can probably solve most dynamic storage/iteration situations in a more straightforward GHPython manner I imagine. The one issue I can think of here is that the DataRecorder
doesn’t expire downstream components while recording (if I recall, might be confusing it with the data dam).
Sidenote: Generally speaking for dynamic pipelines, I tend to separate the iterative feedback loopy stuff (which typically happens upstream) from the logging/recording stuff (which typically happens at the end somewhere downstream).
Edit: Slight update to have Reset
go to zero while running:
191228_GHPython_Timer_01.gh (4.5 KB)
Thank you very much. I don’t understand this part of the code because I’m lazy about digging too deeply into scripting. Will give it a try though. (see below)
# Define callback action
def callBack(e):
ghenv.Component.ExpireSolution(False)
# Get grasshopper document
ghDoc = ghenv.Component.OnPingDocument()
# Schedule this component to expire
ghDoc.ScheduleSolution(interval,gh.Kernel.GH_Document.GH_ScheduleDelegate(callBack))
Later… Here is the model using your latest Python timer (191228_GHPython_Timer_01.gh). It’s a Sun/Earth/Moon model with Starlink satellite constellation added:
starlink_2019Dec28a.gh (40.8 KB)
There are two timers, in orange groups, one for Earth/Moon and the other for the satellites. The first thing I notice is that the satellites slow down when the Earth/Moon timer is enabled. I checked and found the same thing is true using two Timer components with Python counters in yesterday’s version: starlink_2019Dec27a.gh (33.4 KB)
Driving two Python counters with a single Timer doesn’t seem to help so maybe the model is just more compute-intensive with both parts moving?
Evolved from a much simpler question in this thread:
P.S. This topic goes way back: May 8, 2015
P.P.S. Here is a version with two Python counters driven by a single Timer:
starlink_2019Dec27b.gh (36.9 KB)