Change specific panel color from C#/Python component

Hi everyone,

I am trying to control the color of an specific panel from a C# component. The color will depend on the result of an operation so if a number if higher than 5 the panel will become green and if not red. My intention is to have the panel just placed in the canvas without being connected to the c# component, and identify that one just by the ID of the panel.

I could achieve this result, however, the panel has always to be connected to the c# component. I do not want this, what I want is to identify the panel by the ID in order not to be connected to the component.

  private void RunScript(string x, System.Drawing.Color y)
  {
    if(this.Component.Params.Input[0].SourceCount > 0)
    {
      if(this.Component.Params.Input[0].Sources[0] is Grasshopper.Kernel.Special.GH_Panel)
      {
        Grasshopper.Kernel.Special.GH_Panel panel = this.Component.Params.Input[0].Sources[0] as Grasshopper.Kernel.Special.GH_Panel;
        panel.Properties.Colour = y;
      }
    }

Python solution is also welcomed!

Can anyone help me? Maybe some c# experts such as @magicteddy

Thanks in advance!

1 Like

Hi,

Is MetaHopper a possible solution ?
Either only to retrieve the GH_Panel object, or directly to set its color.

If you want to find this Panel by ID you would have to loop through all objects on the canvas using something like this

foreach  (IGH_DocumentObject igho in GrasshopperDocument.Objects)
    {
      if (igho.InstanceGuid == your_Panel_ID)
      {
        GH_Panel gp = igho as GH_Panel;
		 
      }
      
    }
2 Likes

Hi @magicteddy

If possible I would like to avid using plugins, just code.

Hello,

from a C# script component, you can always use a property called “GrasshopperDocument”. It is the current instance of GH_Document. The one currently active. A document contains all instantiated components of your active definition. It also a property called Objects and a method called FindObjects. You can search for panels in different ways. You could use the components ID, which you get if you hover in ribbon over the icon, or by simply checking for the type Grasshopper.Kernel.Special.GH_Panel

Hope this helps

1 Like

Here’s one approach, where we iterate over the Grasshopper document objects and edit named panels:


230816_ColorPanel_GHPython.gh (5.1 KB)

4 Likes

Thank you very much for your comments! I will try to combine your ideas to write the code :slight_smile:

1 Like

I hate Python but since it’s your birthday, you’ve earned my like anyway :smiley:

I copied the content of this script into a new editor.

Is it normal that this script works just in Ironpython and not in Python 3?

230816_ColorPanel_GHPython.gh (5.8 KB)

I’ve not really tested the Python 3 integration yet, but I would expect a lot of this type of Grasshopper meta programming to not work. As I recall it does not have ghenv, which I’m using to access the Grasshopper document objects. You can get to that via the Grasshopper namespace though. I’ll have a look when I’m on my laptop :slight_smile:

1 Like

Hmm, looks like an issue with the Grasshopper namespace interoperability, maybe related to the CPython .NET integration. Anywho, something for @eirannejad to have a look at I’m guessing? For .NET and Grasshopper scripting you can still use the current GHPython component in Rhino 8 WIP (though it’s arguably prematurely hidden, not having feature parity) or run the script component in IronPython mode:


230909_ColorPanel_GHPython.gh (8.8 KB)

1 Like

@AndersDeleuran I see where the problem is. The obj type is Grasshopper.Kernel.IGH_DocumentObject and applying the is operator to it in CPython does not automatically recognize that Grasshopper.Kernel.Special.GH_Panel has the interface implemented. I’ll look into fixing this asap.

RH-76942 “type(obj) is” does not recognize the type correctly

1 Like

It is important to note though that Python 3 and IronPython are not going to be “identical” due to the vast difference between the underlying implementations (Hence why we are still going to support IronPython)

Example:

The correct way to reference a dotnet type in Python 3 (pythonnet implementation) is to get the type from clr.GetClrType module, since imported Grasshopper.Kernel.Special.GH_Panel is a wrapper for the underlying dotnet type. (That is not the case with IronPython)

# this should be on the right hand side of the is operator
is clr.GetClrType(Grasshopper.Kernel.Special.GH_Panel)
2 Likes

I applied a fix for this type check problem. It’s not perfect but the problem area is complicated especially between how IronPython used to work and how Python 3 interop with dotnet runtime is expected to work.

This code works now:

for obj in gh.Instances.ActiveCanvas.Document.Objects:
    if isinstance(obj, gh.Kernel.Special.GH_Panel):
        if obj.NickName == Panel:
            obj.Properties.Colour = Color

type(obj) is gh.Kernel.Special.GH_Panel will not work because the returned object is of type IGH_DocumentObject. It’s more pythonic to type check using the isinstance() method anyway. (Types can customize their type check logic when called from isinstance())

TLDR

C# allows types to explicitly define members for an interface they are adopting:

interface ISayHello
{
    string SayHello();
}

class Person : ISayHello
{
    public string Name { get; }

    // note the ISayHello. part and lack of access modifier e.g. public/private
    // this makes the method explicitly accessible from ISayHello
    string ISayHello.SayHello() => "Hello!!!!!!"
}

When an instance of this type is access with type Person p = new Person();, you are not allowed to call p.SayHello() because that method is explicitly accessible when you have a variable of type ISayHello. So to make it work you have to ((ISayHello)p).SayHello()

Now here is where it gets complicated:

interface ISayHelloInFarsi
{
    string SayHello();
}

class Person : ISayHello, ISayHelloInFarsi
{
    public string Name { get; }

    string ISayHello.SayHello() => "Hello!!!!!!"
    string ISayHelloInFarsi.SayHello() => "Salaaaam!!!"
}

The type person ends up having two SayHello() methods and that’s okay because they are explicit to their own interfaces. so this is how they would be accessed:

((ISayHello)p).SayHello();           // "Hello!!!!!!"
((ISayHelloInFarsi)p).SayHello();    // "Salaaaam!!!"

This gets complicated in python when you have access to the variable p. You can not access either of these methods directly in IronPython. Instead you have to:

ISayHelloInFarsi.SayHello(p) # notice we are passing p to the method

Pythonnet has taken a different approach. In the context of Grasshopper, all objects return from Grasshopper.Instances.ActiveCanvas.Document.Objects are of type IGH_DocumentObject. Pythonnet automatically wraps all these instances with the return type which is IGH_DocumentObject:

import Grasshopper

for obj in Grasshopper.Instances.ActiveCanvas.Document.Objects:
    print(type(obj))  # would print <class 'Grasshopper.Kernel.IGH_DocumentObject'>

There was a bug in the runtime that would return False to isinstance(obj, Grasshopper.Kernel.Special.GH_Panel) although the actuall type of returned object is GH_Panel. This bug is fixed.

However, statements below would work in IronPython:

# IronPython
type(obj)     is gh.Kernel.Special.GH_Panel     # returns True
type(obj)     == gh.Kernel.Special.GH_Panel     # returns True
obj.GetType() is gh.Kernel.Special.GH_Panel     # returns False
obj.GetType() == gh.Kernel.Special.GH_Panel     # returns True

But they will not work in Python 3. Making them work require a bigger change in Pythonnet that could complicate the type system.

# Python 3
type(obj)     is gh.Kernel.Special.GH_Panel     # returns False
type(obj)     == gh.Kernel.Special.GH_Panel     # returns False
obj.GetType() is gh.Kernel.Special.GH_Panel     # returns False
obj.GetType() == gh.Kernel.Special.GH_Panel     # returns False
2 Likes

I am keeping a YT to fix the underlying type(obj) problem:

RH-81135 Python 3 type(obj) does not return leaf (last concrete type) type

Hi Ehsan,

Is the code above supposed to work in Rhino 8.6.24079.11001, or not until the next RC? I’m not sure it picks up any text panels (or any components) on my setup. When I run:

import Grasshopper as gh

for obj in gh.Instances.ActiveCanvas.Document.Objects:
    print(isinstance(obj, gh.Kernel.Special.GH_Panel))

With the Python 3 component itself, a text panel and a Swatch placed, I get:

False
False
False

The change is going in 8.7 - I usually put these bigger changes in the next version to get more testing internally as well.

I understand completely. I look forward to trying out 8.7. Cheers for the fix!.

1 Like

I’ve had a quick play and introspection of GH components works in 8.7, in CPython components now. Cheers!

1 Like