Custom Python modules not referenced from scripts directory any more?

Hi,

I’ve just recently upgraded to Rhino 8 and was glad to see that the script editor is a huge improvement over the old one, especially on macOS! Thanks. :clap:

I’ve noticed that my custom Pythonic modules that I’ve created over the years and frequently use in Rhino and Grasshopper scripting, can’t be import’ed any more.

Is the Rhino specific scripts directory (i.e. /Users/diff-arch/Library/Application Support/McNeel/Rhinoceros/8.0/scripts) still referenced in Rhino 8?

Most of my modules have the following structure:

graphtools
├── __init__.py
├── dcel.py
├── dijkstra.py
├── edge.py
├── graph.py
├── ...

What used to work in GHPython was simply to import from the module in question:

from graphtools import Graph

In Rhino 8, I get an error in both legacy IronPython and CPython:

Has the scripts directory been abandoned or am missing something else here?

EDIT:

I’ve posted a workaround for macOS below that concludes this thread, even if its questions unfortunately remain unanswered!

@eirannejad, do you maybe know about this?

The first thing I would check, is if you are using the correct Python installation / Global or Virtual environment.

Can you type “python --version”.
After that you might type “pip list” to yield all installed modules for that Python installation.

If it does not find the module, the most obvious reason is that its not installed. This is what is saying. Especially the global installation can easily being messed up, by installed different tools/apps. Other potential problems can be related by accidentally installing similar named or tampered libraries, or that you use an unsupported Python which forces your system to build native libraries on your PC.
If that fails, you might have not correctly installed the library (unnoticed),

I think you misunderstood my post above, but thanks for the reply.

ah okay, so you are talking about your own written modules, not your selection of pip modules. I see.

Exactly, it used to be the case that you could put your custom scripts and modules (i.e. folders with scripts) into the Rhino specific scripts folder, and upon relaunch, Rhino would reference all scripts within automatically. It would add them to the IronPython path environment.

What you still could do is creating a local pip module. You don’t need to upload it to PyPi.

Although it is not so convenient I guess.

That’s too inconvenient indeed! I would have to package about 25 custom modules and around 40 single scripts…

I mean, there are also advantages in creating packages.

You can version control them and use various versions in different scripts. You get rid of the problem of keeping multiple variations or to migrate all your script immediately, as soon as you change the library.
With such a dedicated package you can collaborate with others.
You can deploy it decentralised on an artefact management system and you can redeploy them easily on every system. This also helps in setting up a CI/CD pipeline and to reduce the mental load on making new releases.
You force yourself to apply proper encapsulation and to create dedicated testing. Without that you might create more fake libraries, hard to maintain constantly violated DRY and other principles. A nightmare if another developer needs to use your code.
You can “compile” for better performance and shorter build times.
You might also being forced to re-factor your code-base for the purpose of making it a package. This might be worth it alone. Relying on loose scripts, which you might not be able to change for backwards compatibility, is rather a disadvantage.
I bet you can reduce your code-base to a great extend if you design it as a true utility library.
I mean I see the point in keeping things simple. But it sounds to me that you already have a large ecosystem, and for this reason, it is important to question it frequently anyways…

1 Like

Have you tried doing this as a workaround?

import sys
sys.path.append(mypath)

# other imports here

Also you can print your sys.path to find all the available locations including the site-packages directory associated with your interpreter. You can put your files in there to make them importable.

and this discussion has prompted me to discover the site module:

import site
site.getsitepackages()
# lists global site package directories
site.getusersitepackages()
# returns the user-specific site package directory

Yes, just now and it doesn’t seem to work either? I’m still getting the no module exception.

I’m still hoping that one of the developers can chime in and take a look.

OK, I’ve checked the search paths for modules for Rhino 7 and Rhino 8 with print(sys.path), and compared them.

“/Users/marc/Library/Application Support/McNeel/Rhinoceros/8.0/scripts” is definitely missing in the search paths of Rhino 8, but “/Users/marc/Library/Application Support/McNeel/Rhinoceros/7.0/scripts” is still included in those for Rhino 7 and the previously installed Rhino 8 WIP.

Again can somebody please confirm whether the scripts folder has been abandoned or not?

And can somebody please check if the same is the case on Windows?

@AndersDeleuran Would you be so kind? Thanks.

In the same Grasshopper file, the legacy GHPython component is still able to import from the scripts directory, whereas the new Script component, configured for Python 3, can’t.

Interesting.

Also adding the path manually doesn’t work like mentioned previously.

Here’s a workaround for getting back a script folder where you can put your Python scripts and modules to be auto-loaded when Rhino starts up!

I post this because:

  • the old Rhino scripts folder seems to have been abandoned
  • no Rhino developer was able to offer an alternative or even an answer to what happened to the old scripts folder
  • adding paths with sys.path.append() is currently also not working, on macOS at least

My hope is that someone with a similar workflow than me, may find this helpful!

How-to:

Let me preface this by saying that this method is a workaround, and that you should absolutely keep a backup of your scripts, since an upgrade of Rhino may or may not overwrite what we are going to put into place.

I’ve discovered that on macOS, Rhino 8 - in good Linux fashion - creates a dot directory in your home folder called .rhinocode. It’s an invisible directory that you can gain access too by opening a Terminal (Applications/Utilities/Terminal.app) and typing in open .rhinocode.

Now, Rhino automatically adds the following paths to sys.path:

  • /Users/<user>/.rhinocode/py39-rh8/site-envs/default-8SYS74YE
  • /Users/<user>/.rhinocode/py39-rh8/site-rhinoghpython
  • /Users/<user>/.rhinocode/py39-rh8/site-rhinopython
  • /Users/<user>/.rhinocode/py39-rh8/site-interop
  • /Users/<user>/.rhinocode/py39-rh8/lib/python39.zip
  • /Users/<user>/.rhinocode/py39-rh8/lib/python3.9
  • /Users/<user>/.rhinocode/py39-rh8/lib/python3.9/lib-dynload
  • /Users/<user>/.rhinocode/py39-rh8/lib/python3.9/site-packages

I’ve substituted my username for the generic <user>. Yours is different.

You can verify the paths by opening a script editor in Rhino or Grasshopper and running the following script:

import sys

print(sys.path)

Knowing this, we can possibly add our scripts and modules to any of these directories, and they’ll get loaded with the rest, right?

I’ve tried putting a module of mine into /Users/<user>/.rhinocode/py39-rh8/site-rhinoghpython and after relaunching Rhino, I was able to successfully import the module in Grasshopper Python.
This directory doesn’t seem to be scanned recursively for subdirectories though, but the following path is:

/Users/<user>/.rhinocode/py39-rh8/site-rhinoghpython

We can reinstate a scripts directory there, but remember that it might get purged when Rhino is upgraded!
So, we can take this a step further by using a symbolic link instead. This will make sort of a shortcut to a directory somewhere else on your Mac. When the symlink gets deleted, the linked directory remains untouched.

First I recreated a scripts directory, where it was previously located in /Users/<user>/Library/Application Support/McNeel/Rhinoceros/8.0 . This is where I will dump my custom modules and scripts, but you can obviously chose another location.

All that remains is symlinking this directory to /Users/<user>/.rhinocode/py39-rh8/site-rhinoghpython .
Again, open a Terminal and use the following command to put it in place:

ln -s "/Users/<user>/Library/Application Support/McNeel/Rhinoceros/8.0/scripts" ".rhinocode/py39-rh8/site-rhinopython/scripts"

If you choose a different location, you need to change the first path here.

Simply relaunch Rhino and you’ll be good to go.

I’ll use Python 3 from now on, but if you want to do the same for legacy IronPython 2.7 scripts, I would recommend to use a separate scripts folder and symlink to rhinocode/py27-rh8/site-rhinopython/scripts instead.
I haven’t tested this yet, but I don’t see why it shouldn’t work.

3 Likes

Thanks, @diff-arch - I’ll give this a try on Windows (11) when I can nail down some time soon.

Is there a reason we use Rhino version-specific Scripts folders as opposed to version-agnostic, to begin with?

Thx

Alan

No, it’s just my preference. You should be able to use any folder that you have permissions for.

1 Like