I’m testing if I could utilise sticky as a “global variable” in a script, where the same value is needed in several locations and inside clusters. Trying to make it updatable.
I can get the “receiver” to update, but once it is inside a cluster it’s just off the radar.
How to, either:
update the cluster, or
find whether the receiver is inside the cluster and update that
Hi @Will_Wang ,
thank you for this. It’s interesting, but IMO the need for a dummy parameter kind of negates the purpose of the sticky in this case - as I could just as easily send the values through a hidden wire.
But as a solution I’m trying to comprehend what’s happening in your script. It seems that you did not change any code (including my typo in the sender), just added an input and hooked it up to the receiver. What’s the mechanics here - what now gets recognised and thus, updated?
And why does it stop working, if I unhook the input from the receiver OR if I add some data to it? So, the dummy input really needs to be empty.
I needed to expire the cluster component, as well as the “Receiver” component inside the cluster.
The components that need to be expired are still recognised by their NickName, which is set to “RECEIVE”. In this way, I don’t need to expire the entire document.
from scriptcontext import sticky
import Grasshopper as gh
if not sticky.has_key("x"): sticky["x"] = x
if not sticky.has_key("y"): sticky["y"] = y
if sticky["x"] != x or sticky["y"] != y:
sticky["x"] = x
sticky["y"] = y
for obj in ghenv.Component.OnPingDocument().Objects:
if obj.NickName == "RECEIVE":
obj.ExpireSolution(True)
elif type(obj) == gh.Kernel.Special.GH_Cluster:
obj.ExpireSolution(True)
for comp in obj.Document("").Objects:
if comp.NickName == "RECEIVE":
comp.ExpireSolution(True)
Hi Toni, this is more of an alternative solution, which may fit your needs better:
I’ve recently switched from using the Rhino runtime sticky when requiring global variables across one Grasshopper definition. To instead writing and reading persistent data directly to the Grasshopper file ValueTable. This has the added benefit of actually “sticking” between sessions. And can thus be used to e.g. remember baked objects, element tracking across Grasshopper/Revit. Here’s a quick GHPython example, using the standard json module to pack/unpack the data (i.e. you can have a JSON database hidden in there):
Hi, apart from that. A great way for this use-case is to implement the observer pattern. Create an “observable” (or “subject” or “notifier”) and store it in the sticky. Then any observer(=your “Foo” component) must subscribe once. Whenever the observable changes its states, all subscribers will get notified and update. The observable can also persist itself (e.g. into a .json)!
@AndersDeleuran Thank you for this input. I remembered seeing this sometime, somewhere here. Seems a bit more intelligent version, as the values are actually persistent. Is the ValueTable reachable from inside a cluster, as this is more document dependant (afaik)?
However, this still needs to be updated via the method I used with sticky, or the method @TomTom is outlining here. @TomTom could you clarify this with an example? How do you subscribe and get notified independently?
Something like this might work, although the cluster document nesting might become quite brittle (a cluster is essentially a Grasshopper document within a Grasshopper document):
@AndersDeleuran If the sticky values are visible through all layers, including inside nested clusters, would it actually make sense to combine the ValueTable and sticky methods? I.e. use Valuetable to store the values, and sticky to distribute them. So, every time document opens, sticky variables are populated by ValueTable values.
Uh that’s a good question. To be honest, I’d avoid clusters if at all possible when it comes to this type of metaprogramming. As they’ve historically demonstrated “odd” expiration behaviour and general “wonkiness”. If you can implement your business code in GHPython on the top-level document, that’d likely be simpler/more predictable.
Edit: Hang on, this (selecting the last document in the Grasshopper document server list) appears to work with deeper cluster nestings as well:
@AndersDeleuran That seems promising… I’ll try implementing this for my needs…
For some background, I’m actually trying to package small helping snippets for robot toolpath creation (using plugin) for more visually appealing and hassle-free components. Hense the need for clusters. And as many of these need info on the robot, tool, and base - I thought about having that as a floating sticky variable, so there would not be a need to input that info to every “target” component, for instance. But, the info needs to update inside the cluster if the user changes any of these.
Currently, I have packaged this and send through a single hidden wire. I want to get rid of that input on the cluster components.
Ah yes, in that case it might be hard to avoid clusters. That said, depending on the plug-ins you’re using, you might be able to implement them in GHPython using node-in-code.
Hi @AndersDeleuran
I’m not sure yet, how relevant this is in this case, but I created this small script that retrieves the Top document from inside a cluster, even inside nested clusters (getting to the Top recursively).
This could be used to update the Parent Top document from inside the cluster. For instance, when changing sticky values from inside cluster.
Ok @AndersDeleuran@Will_Wang
I think I came up with the “ultimate” solution using sticky. This could be easily adjusted for ValueTables as well.
Now the Sender works from inside a cluster as well, even if it is inside a nested cluster. And the Receiver works even if it is inside a nested cluster. The update process starts from the Top parent document, and Clusters are searched and updated recursively as well.
from scriptcontext import sticky
import Grasshopper as gh
def updateReceivers(name, doc):
for obj in doc.Objects:
#if receiver is present in the document
if obj.NickName == name:
obj.ExpireSolution(True)
#if receiver is inside a cluster
elif type(obj) == gh.Kernel.Special.GH_Cluster:
obj.ExpireSolution(True)
updateReceivers(name, obj.Document(""))
def getTopDoc(d):
owner = None
topDoc = d
try:
owner = d.Owner
topDoc = getTopDoc(owner.OnPingDocument())
except:
pass
#type GH_Document
return topDoc
#initialise keys for the first use
if not sticky.has_key("x"): sticky["x"] = x
if not sticky.has_key("y"): sticky["y"] = y
#if values have been updated,
#update receivers
if sticky["x"] != x or sticky["y"] != y:
sticky["x"] = x
sticky["y"] = y
#update all receivers recursively
#starting from the TOP parent document
mainDoc = getTopDoc(ghenv.Component.OnPingDocument())
updateReceivers("RECEIVE", mainDoc)
Hi! I know that this is a python post, but I was trying to create something similar in C# with a sender component to a clustered receiver, can anyone help me, Plaese?
Sender:
using System;
using Grasshopper.Kernel;
public class SenderComponent : GH_Component
{
public SenderComponent() : base("Sender", "S", "Send numeric data to receiver", "Test", "Data") { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddNumberParameter("Data", "D", "Numeric data to send", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess DA)
{
double data = 0;
if (!DA.GetData(0, ref data)) return;
// Get the nickname of the target receiver
string targetNickname = "ReceiverNickname";
// Find the remote component with the given nickname within the cluster
GH_Component targetComponent = null;
foreach (IGH_DocumentObject obj in OnPingDocument().Objects)
{
if (obj is GH_Cluster)
{
GH_Cluster cluster = obj as GH_Cluster;
foreach (IGH_Component clusterComponent in cluster.ClusterObjects(true))
{
if (clusterComponent.NickName == targetNickname && clusterComponent is GH_Component)
{
targetComponent = clusterComponent as GH_Component;
break;
}
}
}
}
// Check if the target component was found
if (targetComponent != null)
{
// Send data to the target component
targetComponent.Params.Input[0].AddVolatileData(new GH_Path(0), 0, data);
}
// Expire solution to update the changes
ExpireSolution(true);
}
public override Guid ComponentGuid
{
get { return new Guid("GUID_HERE"); }
}
}
Sender:
using System;
using Grasshopper.Kernel;
public class ReceiverComponent : GH_Component
{
public ReceiverComponent() : base("Receiver", "R", "Receive numeric data from sender", "Test", "Data") { }
protected override void RegisterInputParams(GH_InputParamManager pManager) { }
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Data", "D", "Received numeric data", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
double data = 0;
// Receive data from the sender component
DA.GetData(0, ref data);
// Do something with the received data...
// Output the received data
DA.SetData(0, data);
}
public override bool Read(GH_IReader reader)
{
// Perform any additional reading here if needed
return base.Read(reader);
}
public override bool Write(GH_IWriter writer)
{
// Perform any additional writing here if needed
return base.Write(writer);
}
protected override void ExpireDownStreamObjects()
{
// Expire downstream objects to trigger a re-computation when data is received
OnPingDocument().NewSolution(true);
base.ExpireDownStreamObjects();
}
public override Guid ComponentGuid
{
get { return new Guid("GUID_HERE"); }
}
}
Hello TONI,
Thanks for your script; it’s excellent!
I’ve opened it on the Rhino 8 Mac, and it works well.
The icon is just “OLD,” I think because it was written in Python 2, and now Rhino 8 uses Python 3.
Is it possible to update it to Python 3 (I can’t find what I need to change)?
Thanks again,
Roy
Hi @lopez ,
I tried porting this to Python3 today, but hit a wall, when trying to update the receiver inside the cluster.
There seems to be a known issue between Ironpython and Python3.
So, currently this does not seem possible. I think you can just copy-paste the code to Ironpython component and that should work fine. Just remember to name the receivers “RECEIVE”.
I’m trying to store geometry values (something coming out of a geometry component) in the sticky dictionary to retrive it using other ghpython component but when assigning the output variable the sticky value I get an error.
Does sitcky supports other types beside strings, integers or floats?
import rhinoscriptsyntax as rs
from scriptcontext import sticky
if "x" not in sticky:
sticky["x"]=x
outPut=sticky["x"]
print outPut
a=outPut
Runtime error (NullReferenceException): Object reference not set to an instance of an object.
Traceback (most recent call last):
SystemError: Object reference not set to an instance of an object.