clr.AddReference() for compiled GHPYs

Hi @piac,
thanks for your amazing work!
I’m currently compiling a script that uses some additional .NET .dlls referenced via clr.AddReference().
This works fine as long as the referenced dlls are kept directly in the Libraries directory, however if they are put into an additional folder, they are no longer accessible.

Can I check directly from the script for the path from which the compiled .ghpy is loaded, so I could add it to the sys.path? Quite possibly a noob question.

Thanks, Ondrej

Hi @ondrej-vesely

please start new topics for new conversations. You are entitled!

As far as I understand (this is an IronPython topic, not a Rhino topic, really, per se), sys.path is not used for clr.AddReference, because that is for a .Net assembly, not for a Python package/module.

However, when searching for a reference fails, Rhino is still invoking its own assembly resolver. That one automatically looks into all folders where a plug-in is installed, including the Libraries folder. @will might know more, as I know he has been making adjustments to that lately.

In the clr namespace there are other overloads, like clr.AddReferenceToFileAndPath(), etc. You might need to find the right system that works for your particular installation goal.

Possibly this will tell you which library is being called in a particular moment:
https://msdn.microsoft.com/en-us/library/system.reflection.assembly.getexecutingassembly(v=vs.110).aspx?f=255&MSPPError=-2147217396

System.Reflection.GetCallingAssembly().Location or
System.Reflection.GetExecutingAssembly().Location

Would you check and report?

Are you planning on shipping the GHPY aside the other .dll? Or the GHPY in its own folder, and the other .dll in another?

import clr
from System import Reflection

clr.AddReference("GeoAPI.dll")
clr.AddReference("ProjNet.dll")
import GeoAPI, ProjNet

print Reflection.Assembly.GetCallingAssembly().Location

returns: C:\Program Files\Rhino 6\Plug-ins\IronPython\Microsoft.Dynamic.dll

My goal is to have the .dlls loaded, as long as they are in the same directory as .ghpy itself.
The reason for it is that from my experience Library forder can get quite crowded and users tend to sort various Plug-ins themself to separate folders. Setup where one folder in Library contains all dependencies and .ghpys for each plug-in seems preferable to me, since it’s harder lose some files by accident.

Anyway, now I tried shoving the .dlls into “…\Rhino 6\Plug-ins\IronPython”, which at least conviently hides them from any careless users that could move or delete them from Libraries by accident, and there are always accesible from clr.AddReference().
Are there any obvious disadvantages to that aproach?

What about GetExecutingAssembly() ?

Yes, nobody knowing that they are there for a reason.

GetExecutingAssembly() gives the same result, tried for both Procedural Script Mode and compiled code.

You need to try after it’s compiled. When it’s in the evaluation mode, for sure it won’t work…

That’s what I meant by compiled code.

It looks like enquiring where the compiled assembly is located (from Python) is a little more involved than what I first thought:

Wonder if this could be of help.

EDIT: I run ahead of myself a bit here…

You can use this:

a_component_id = System.Guid('YOUR-COMPONENT-ID-4fde-acc6-4624ed345388')
Grasshopper.Instances.ComponentServer.EmitObjectProxy(a_component_id).Location

this needs to happen after the component has loaded. So make sure the clr code happens only at request (not when GH loads). To set your component ID as a fixed value, you will need to compile the code yourself, copying the code to the clipboard.

Then, construct the right path and use clr.AddReferenceToFileAndPath().
I hope this helps,

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

Works, many thanks!
This method should also work for compiling using the wizard, since you can use .getComponentGuid().

def RunScript(self):

    id = self.get_ComponentGuid()
    path = Grasshopper.Instances.ComponentServer.EmitObjectProxy(id).Location
    directory = path.replace("*YourAssembly.ghpy*", "")

    clr.AddReferenceToFileAndPath(directory + "YourNet.dll")

The downside is that this way, you have to import inside every component, instead of importing just once.

There’s also a sort of a hack to this problem.
From my testing, clr.AddReference() will be able to always find the .dll, if there is any .gha assembly loaded from it’s path.
I guess you could create some dummy .gha assembly just to add the reference and distribute it with your .ghpy.

Best regards and thanks again Giulio!

Btw.: What about GhPython.Assemblies.PythonAssemblyInfo.Location? Couldn’t this be used to access the path on GH load?

1 Like

Great, I actually tested it before too.

I agree, but there’s no other easy way that I can see right now.

That cannot be used currently. But it will. RH-47576. I just fixed it.

Don’t do this. Just use os.path.dirname.

What SR of Rhino 6 are you using? We recently fixed an issue regarding that. RH-43622.

Finally, you don’t need to do the clr.AddReference() inside RunScript. It could be done in the BeforeSolveInstance method. Remember to call the base method, for everything to work as expected.