Scritping in R8, ironpython2 and python 3?

Hi.

Am working on a workflow that depends on LBT-components for calculation, as well as LBT modules for calculations inside my own python code.

I’m trying to cross over into Rhino8, mainly with regards to being able to publish a plugin fairly easily, but am starting to become a bit confused.

LBT components work in R8, and I installed via pollination.
LBT in R8 runs in the old editor, and old version of ironpython.

But moving forward, I thought it would be sensible to transition into the new editor and python 3, or at least iron python 2.

I tried moving some code from “LB Deconstruct Data” to iron python 2, processing just windspeed from epw-import, but:

Failed to import ladybug:
No module named ladybug.datacollection
IronPython.Runtime.Exceptions.ImportException:

And in python3, component, I got the following:

AssertionError: _data must be a Data Collection. Got <class ‘IronPython.NewTypes.System.Object_2$2’>.

So it seems that it cant access the data, as it is on iron python format.

Am I totally off track, and should be happy with the old editor, and older version of iron python?

Or is there something that I’m doing wrong?

Sincerily,
HappyBuddha

Hi @LittleBuddha ,

Yes the new Rhino python landscape is confusing (IMO). The short version is: you should continue to use the older GhPython components whenever you want to work with the LBT SDK or LBT objects in your Rhino/Grasshopper files.


The slightly longer version is:
Rhino & Grasshopper have 3 distinct python interpreters now; a 3.x one, an IronPython one, and the older GhPython one. These three interpreters are fully separate from one another, and while it is technically possible to pass some very simple objects between interpreters, it is not recommended, and certainly complex objects like LBT rooms, models, etc cannot be easily moved between them.

The three interpreters do not share system path or dependency environments, and so installing the LBT libraries on the GhPython one will not make them available on the IronPython or v3 ones.

Unless / until the LBT components themselves are updated to work in the ‘new’ interpreters, we will just need to stick with the ‘old’ one.


hope that helps!
best,
@edpmay

Hi @edpmay

And thanks for your prompt and helpful answer.

Basically hands off, don’t do it and continue to embrace iron python version 1.

Would have been nice to get access to the new editor though, when working with the components.

Some of my components are marked old, and some not. Which I also haven’t figured out why.

Yours,
Little and fairly happy Buddha

@edpmay gave the right recommended answer here.

There’s a lot of fixing and improvements to the new Rhino 8 script editor that Ehsan from McNeel has been working on for the past few years (and is continuing to work on). He has done a great job but, because of all the changes, the earliest that you will see us move the Ladybug Tools components over to the new Script Editor is Rhino 9.

So using the old GHPython component and old IronPython environment is currently the best way to use Ladybug Tools for now in Rhino 8. If you need to summon an old GHPYthon onto your canvas, just double-click it and search for #GHPython (with the #). That will let you bring it up instead of the new script component.

Also for this:

Ehsan gave us a method that we can call to turn the tag off in our old GHPython components. You should find that they are all gone from your Ladybug Tools components if you’ve installed the latest version of Ladybug Tools and run the LB Sync Grasshopper File component to update all of the LBT components on your canvas.

If you want to get the OLD tag to go away on your own custom components, the method Ehsan added into the Rhinocommon SDK is Component.ToggleObsolete(False). You can see that I wrapped it into a function we use in Ladybug Tools here:

So you can make use of it inside your own GHPython component in the following way:

from ladybug_rhino.grasshopper import turn_off_old_tag
turn_off_old_tag(ghenv.Component)

Lastly, I’ll add that the latest Pollination installers add the Ladybug Tools core libraries to the Python paths of the Rhino 8 Script Editor. So, if you use the latest Pollination installer, you should not have any more import errors if you try pasting code from LBT’s GHPython components into a new ScriptEditor component. This means that you can technically use the LBT core libraries in the Rhino 8 script editor if you want. Just beware that the environment of these ScriptEditor components doesn’t really talk to that of the GHPython component as Ed mentioned.

But there is a lot of power and potential in the new script editor, particularly for things like making your own Rhino commands with the LBT SDK. For example, the Rhino 8 ScriptEditor is how I made these Environmental Analysis commands that we have been shipping with the Pollination Rhino installer.

Thanks a lot to both @chris and @edpmay for their answers, the way forward is much more clear to me now.

@chris, thanks so much for this!! As a semi-OCD I am so happy to get rid of those tags

Hi Gang.

Returning to this, after a sojourn in other fields.

I have a series of scripts, that are in the original ironpython editor, and interact with LBT components. Both as pre and post processing. This therefore means that its the original iron python all the way, if I understand everything correctly.

The components refer to my own python based libraries for a series of functions, and as of now the installation needs to include that code in folder on the PC. But I would want to publish a plugin of the components.

Using IronPython, would mean not being able to use the nice new publishing features of Rhino8, if I understand correctly?

I’ve tried reading up on the old way of publishing, but do struggle a bit with the neccesary structure, yaks, and etc.

Yours,
Little Buddha

Hi @LittleBuddha ,

It is a good question. I for one have not really found a good ‘built-in’ way to distribute python plugins for Rhino. In case it is helpful, the process we use for the Honeybee-PH Plugin looks like the following:

  1. We have offloaded most of the ‘work’ to dedicated libraries which we host on PyPi (Honeybee-PH, PHX, PH-Units).
  2. For installation, we make available a .gh file which includes an ‘installer’ GH-component patterned on the old Honeybee Food4Rhino installer (pre-Pollination). This installer file allows the user to click ‘install’ in GH, and all the necessary back-end setup is done for them.
  3. When run, this installer component runs pip install ... for the required packages against the ladybug-tools python installation.
  4. It also downloads the .gh components from our GitHub repository and puts them in the user’s Rhino directory so they are available on Rhino’s restart.

Things to consider:

  • This process works fine on MacOS, but Windows is harder. Specifically: the user MUST run Rhino in ‘Admin’ mode to be able to Pip-install into the ladybug-tools python installation.
  • If the user is NOT in ‘Admin’ mode, python has some unfortunate behavior which will mess things up. So you need to be sure to protect against that.
  • This means some teams with locked down corporate systems might (will) struggle to use such an installer.
  • I would definitely recommend some sort of ‘versioning’ scheme be applied so you can easily keep track of which version your users have installed. This helps a lot on trouble shooting. We copied the LBT automatic version numbering setup which is run by GitHub Actions - I highly recommend something along those lines.

hope that helps - and if you find a better ‘built in’ method for distribution I’d love to know it!

best,
@edpmay

I will have to have a think on this.

Thanks @edpmay

Hi @edpmay,

Thanks for sharing the HBPH pipeline – super helpful!! I have a question about your component workflow.

How do you actually develop and update components?

From the installer, I see .ghuser files get copied into Grasshopper\UserObjects, but I’m not clear on what the development side looks like.

Do you keep components inside a large .gh file and edit them there, then export/update both the .ghuser and .py from the canvas (e.g. using something like the Export UserObject component)?

Or do you write the logic directly in .py files and then generate the .ghuser files from that? I am very tempted to follow this approach, and maybe use something like COMPAS’ componentizer.

Cheers,
Gustavo

Hi @gzorzeto , Thats a good question: the component part is the most manual piece. Most everything else is automated except this. The way I currently do it is that I develop new components ‘as-needed’, and use the manual Rhino/Grasshoper ‘Create User Object’:

Once built though, I have a small utility component that handles the wrest: It grabs the ghuser file, pulls out the actual python code, and saves both the .ghuser and a backup .py file to my ‘dev’ directory. I run it manually from within Grasshopper after completing the component construction:

This way I can keep the ghuser and script in my dev repo with version-control and distribute them from there.

It is not great, but I am not aware of any auomtated way to manage those user-object ghuser files? Would be super curious it there is! But thanks to the ‘facade’ pattern here, the components themselves almost never really change once built -the ‘work’ all happens in the behind-the-scenese scripts, which are normal .py files and can be managed using normal automation tooling.

hope that helps!!

scraper:

"""
Updates all the GH-Components in the Source dir (Github)
-
EM February 11, 2025
"""

ghenv.Component.Name = "__HBPH__Util_Update_GHCompos"
ghenv.Component.NickName = "HBPH_Update_Source"
ghenv.Component.Message = 'FEB_11_2025'
ghenv.Component.IconDisplayMode = ghenv.Component.IconDisplayMode.application
ghenv.Component.Category = "Honeybee-PH"
ghenv.Component.SubCategory = "00 | Utils"
ghenv.Component.ToggleObsolete(False)

import os
import Grasshopper.Kernel as ghK
import shutil
from GhPython.Component import ZuiPythonComponent


class NamespaceComponentsOntoCanvas():
    """Context Manager class to add all of a namespace's GH-Components to the Canvas.
    
    #Usage: 
    >>>  with NamespaceComponentsOntoCanvas("HB-REVIVE", hb_revive_source_dir, ghdoc, ghK) as compos:
    >>>      ... ## do some things with the components
    """

    def __init__(self, _namespace, _source_dir, _ghdoc, _ghK):
        # type: (str, str, Any, Any) -> None
        self.namespace = _namespace
        self.source_dir = _source_dir
        self.compos = []
        self.ghdoc = _ghdoc
        self.ghK = _ghK
    
    def __enter__(self):
        """Add all of the namespace components to the canvas."""

        print('Adding all {} the Components to the Canvas'.format(self.namespace))
        ghuser_file_names = os.listdir(self.source_dir)
        
        for compo_name in ghuser_file_names:
            if '.' == compo_name[0]: continue # Fucking Mac OS....
            
            #if self.namespace not in compo_name: continue
            
            if not str(compo_name).startswith(self.namespace): continue
            if '__' == compo_name[:2]: continue
            
            print "Adding: {}".format(compo_name)
            compo_address = self.source_dir + compo_name
            compo = self.ghK.GH_UserObject(compo_address).InstantiateObject()
            self.ghdoc = ghenv.Component.OnPingDocument()
            self.ghdoc.AddObject(compo, False)
            
            # --  keep track of the components added to the canvas
            self.compos.append(compo)
            
        return self
      
    def __exit__(self, exc_type, exc_value, exc_traceback):
        """Remove all the components added to the canvas."""

        print('Cleaning Up the Canvas')
        for compo in self.compos:
            self.ghdoc.RemoveObject(compo, False)


def copy_py_code(_gh_component, _target_path):
    # type: (ZuiPythonComponent, str) -> None
    """Copy the ghuse component python code over to a save file."""
    
    target_path_ = os.path.join(_target_path, _gh_component.Name + '.py')
    
    if 'Code' not in dir(_gh_component):
        # -- Value Lists items don't have Py code
        return None

    if os.path.exists(target_path_):
        os.remove(target_path_)

    print 'Writing {} code to: --> {}'.format(_gh_component.Name, target_path_)
    with open(target_path_, 'wb') as f:
        f.write(_gh_component.Code.encode('utf-8'))

    return None


def copy_ghuser(_gh_component, _source_path, _target_path):
    # type: (ZuiPythonComponent, str, str) -> None
    """Copy ghuser component to the save file."""

    src_path_ = os.path.join(_source_path, _gh_component.Name + '.ghuser')
    target_path_ = os.path.join(_target_path, _gh_component.Name + '.ghuser')
    
    if os.path.exists(src_path_):
        print 'Copying: {} --> {}'.format(src_path_, target_path_)
        shutil.copy(src_path_, target_path_)
    else:
        raise ValueError("Error: {} folder is missing?".format(src_path_))

    return None


if _run:
    # - - - - - - HB-PH Components
    hbph_source_dir = str(r"/Users/em/Library/Application Support/McNeel/Rhinoceros/8.0/Plug-ins/Grasshopper (b45a29b1-4343-4035-989e-044e8580d9cf)/UserObjects/honeybee_grasshopper_ph/")
    hbph_save_dir_ghuser    = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_grasshopper_ph/honeybee_grasshopper_ph/user_objects/")
    hbph_save_dir_ghuser_py = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_grasshopper_ph/honeybee_grasshopper_ph/src/")

    print '- '*25, 'Backing up HBPH Components', '- '*25
    with NamespaceComponentsOntoCanvas("HBPH", hbph_source_dir, ghdoc, ghK) as compos:
        for compo in compos.compos:
            copy_ghuser(compo, hbph_source_dir, hbph_save_dir_ghuser)
            copy_py_code(compo, hbph_save_dir_ghuser_py)

    # # - - - - - HB-PH+ Components
    hbph_plus_source_dir = str(r"/Users/em/Library/Application Support/McNeel/Rhinoceros/8.0/Plug-ins/Grasshopper (b45a29b1-4343-4035-989e-044e8580d9cf)/UserObjects/honeybee_grasshopper_ph_plus/")
    hbph_plus_save_dir_ghuser    = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_grasshopper_ph_plus/honeybee_grasshopper_ph_plus/user_objects/")
    hbph_plus_save_dir_ghuser_py = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_grasshopper_ph_plus/honeybee_grasshopper_ph_plus/src/")
    
    print '- '*25, 'Backing up HBPH+ Components', '- '*25
    with NamespaceComponentsOntoCanvas("HBPH+", hbph_plus_source_dir, ghdoc, ghK) as compos:
        for compo in compos.compos:
            copy_ghuser(compo, hbph_plus_source_dir, hbph_plus_save_dir_ghuser)
            copy_py_code(compo, hbph_plus_save_dir_ghuser_py)

    # - - - - - HB-REVIVE Components
    hb_revive_source_dir = str(r"/Users/em/Library/Application Support/McNeel/Rhinoceros/8.0/Plug-ins/Grasshopper (b45a29b1-4343-4035-989e-044e8580d9cf)/UserObjects/honeybee_REVIVE_grasshopper/")
    hb_revive_save_dir_ghuser    = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_revive_grasshopper/honeybee_revive_grasshopper/user_objects/")
    hb_revive_save_dir_ghuser_py = str(r"/Users/em/Dropbox/bldgtyp-00/00_PH_Tools/honeybee_revive_grasshopper/honeybee_revive_grasshopper/src/")
    
    print '- '*25, 'Backing up HB-REVIVE Components', '- '*25
    with NamespaceComponentsOntoCanvas("HB-REVIVE", hb_revive_source_dir, ghdoc, ghK) as compos:
        for compo in compos.compos:
            copy_ghuser(compo, hb_revive_source_dir, hb_revive_save_dir_ghuser)
            copy_py_code(compo, hb_revive_save_dir_ghuser_py)

Wow, this is great. Thank you a lot for sharing!

@edpmay is that dark mode I spot??
Your UI looks great for what its worth :metal:t2:

Busted! Trying my best to keep up with you cool kids :wink: