Change specific panel color from C#/Python component

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