Python component in multiple GH files: how to keep updated?

I have a Python component that I use in multiple GH files. Every time I update the component to fix a bug or whatever, I need to (somewhat laboriously) go to each GH file that uses it and update it to use the new version.

This isn’t a show stopper, just a bit of a pain. But like any programmer I worry (especially when trying to sleep at night) about source control…did I remember to update all the right files?

Is there a cleaner/more reliable way to do this? Can I somehow call the component in the “master” GH file from another file? Convert it to a plugin, perhaps?

Any ideas welcome, thanks!

It is possible to load a Python script from an external file to simplify the process of updating your Python component across several GH files. Here’s how to do it:

  1. First, enable script input on your component by holding Shift and right-clicking.
  2. Next, add a Read File component to your GH file.
  3. To specify the programming language, add the following line at the beginning of your code:
#! python2
#! python3
//#! csharp

This method allows you to centralize your script updates, so that any changes are reflected across all GH files that use this script.

7 Likes

Hops?

I have a script that can distribute the latest C# code in one GH definition to other GH definitions that contain C# nodes with the same name. I don’t know the same approach works for Python, but should be the same principle.

//Inputs: file (string), name (List<string>), target (List<string>)

    var io = new Grasshopper.Kernel.GH_DocumentIO();
    io.Open(file);
    string dir = System.IO.Path.GetDirectoryName(file);

    var doc = io.Document;
    doc.Enabled = true;
    Dictionary<string,Tuple<string,string>> dat = new Dictionary<string,Tuple<string,string>>();
    foreach(var obj in doc.Objects )
    {
      if(obj.GetType().ToString().Contains("CSNET_Script"))
      {
        if(name.Contains(obj.NickName))
        {
          int index = name.IndexOf(obj.NickName);
          string tt = target[index];
          var _source = Grasshopper.Utility.InvokeGetterSafe(obj, "ScriptSource");
          string source = (string) Grasshopper.Utility.InvokeGetterSafe(_source, "ScriptCode");
          string additionalsource = (string) Grasshopper.Utility.InvokeGetterSafe(_source, "AdditionalCode");
          dat[tt] = new Tuple<string,string>(source, additionalsource);
        }
      }
    }

    var ff = System.IO.Directory.EnumerateFiles(dir, "*.gh");
    foreach(var f in ff)
    {
      var _io = new Grasshopper.Kernel.GH_DocumentIO();
      _io.Open(f);
      var _doc = _io.Document;
      _doc.Enabled = true;

      foreach(var obj in _doc.Objects )
      {
        if(obj.GetType().ToString().Contains("CSNET_Script"))
        {
          if(target.Contains(obj.NickName))         
          {
              var elem = dat[obj.NickName];
              var source = elem.Item1;
              var additionalsource = elem.Item2;

              var _source = Grasshopper.Utility.InvokeGetterSafe(obj, "ScriptSource");
              Grasshopper.Utility.InvokeSetterSafe(_source, "ScriptCode", source);
              Grasshopper.Utility.InvokeSetterSafe(_source, "AdditionalCode", additionalsource);
              _io.Save();
          }
        }
      }
   }
1 Like

It depends a bit on whether you’re referring to the existing GHPython component or the CPython/IronPython modes of the new Rhino 8 Grasshopper script editor. And on whether or not this is just for you or for many users (and if those users share a server etc.). Either way, in addition to the methods proposed above, here are some options to consider:


1) The most general, simple and Pythonic approach that should work no matter what:
Structuring all the relevant classes and functions into a Python module and importing/calling these within your component. Meaning that you would just need to update a Python file that lives locally or on a server. Here’s a slide from the in-house Python course I teach demonstrating this:


2) Automating reading and overwriting the component code:
The GHPython component (and presumably the new script editor component) has a Code property, which one can systemically read/overwrite in order to update the code within the component. Meaning that one can develop functions that both “push” and “pulls” code from a central folder structure on e.g. a server, a DropBox, or a GitHub. I’ve used this approach quite extensively in my old research groups, when running workshops, and in-house at BIG. Have a look at the two first code blocks for some examples:


3) Compiling GHPython components to a plugin:
I have never had much luck with and find the workflow pretty cumbersome, but it might work for your needs:


4) Using the plugin options provided by the new script editor:
Again I have not used these new features, but they might be relevant to your goals:

4 Likes

I have had good luck with @AndersDeleuran 's solution #1 (packaging and importing python modules) for plugins. However, just to add a few small personal preference items in case its helpful:

  1. I prefer to separate the actual source code and the modules that Rhino is accessing. This makes it easier for me to create git repository for the package, include tests, virtual-environments, manage package versions, include docs, experiment, etc… without having all of that right in the Rhino scripts directory, or messing with the path in Rhino to point it at some other folder. But in order to shorten the iteration time (ie: make a change in the code, test the code in Rhino, …) I use a free VSCode plugin called ‘fsdeploy’ which watches and copies changed files into the deployment directory as I am editing them. This helps make it much easier to write and then test the edited code in Rhino.


    Note that one big plus to writing in an editor like VSCode is that if you type-hint your functions and methods using the Python 2.7 MyPy style and also use the Rhino-stubs + Grasshopper-stubs, it will recognize the types and you get nice autocomplete and type-checking, if you like that kind of thing.

  2. When writing code and testing in Rhino, using the python ‘reload’ function on your imported module can help shorten the development cycle a lot as well. This reloads the module every GH-component execution, so you can see any newly written / edited code working without rebooting Rhino.


    There are some ‘gotchas’ with reload however, especially if you or any of the packages you interact with use isinstance checks - so I usually put the reload under a ‘Dev-mode’ flag, so I can turn it on to write and test, then turn if off to deploy.

  3. As just a general suggestion, I have been very happy with the pattern where the GH-component functions as only a facade/wrapper to collect the inputs, and display user-messages, but passes off all the actual work to an outside function or, as I mostly prefer, a class for each component. All input validation, work, and output filtering/sorting happen in this module outside GH. The less ‘real’ code in the GH-Component, the easier it is to manage versions and updates, IMO.

best of luck with it,
@ed.p.may

1 Like