Running Grasshopper Scripts on Rhino Linux

It begins.

I’m trying to port some code and get a sense for how things work in the new world. The immediate goal is to create and run the solver on a simple GH_Document. I’m starting from the latest code here: compute.rhino3d/src/compute.geometry/Startup.cs at 9.x · mcneel/compute.rhino3d · GitHub

Startup completes as expected on WSL Unbuntu 24.04, logs and everything. But I’ve been playing in a new endpoint to see what’s safe to call and when. For example, the following three lines in a new /grasshopper endpoint:

```
var _ = Grasshopper.Instances.ActiveCanvas;

var doc = new Grasshopper.Kernel.GH_Document();

var server = Grasshopper.Instances.ComponentServer;
```

The first two complete without issue, but the last one throws:

terminate called after throwing an instance of ‘PAL_SEHException

The GrasshopperEndpoint file is commented out, is there somewhere else in the codebase I can spy on the right way to invoke a script? Tried a few dozen things I can also list out here later if helpful!

2 Likes

Forward motion:

It looks like some of the standard components that are shipping with rhino on linux crash on some sort of load. I’ve had to disable Kangaroo and IOComponents, but now we’re moving again. More soon.

Thanks for being a guinea pig Chuck. There are still a number of low level C++ operations that are “unimplemented” and throw exceptions. If we can repeat the problems you are seeing there’s a good chance it is because of some unimplemented code that we can focus on and get added to the build.

1 Like

Happy to poke around in the dark! Totally appreciate that I’m not always on your highest priority path, too.

Will keep marching and reporting back. (:

2 Likes

Hey folks,

To piggyback on this, (thanks Chuck, that fix worked for one step) the next blocker I’ve found:

  • Grasshopper.Instances.InvalidateCanvas gets called by GH_Document.NewSolution
  • This throws because that brings in System.Drawing which kills the whole deal.

I know McNeel build their own version of Drawing (but compute seems to pull in the microsoft package?)

Stack Trace
  [00:27:41 INF] Starting compute.geometry instance on port 6003

CG  [00:27:41 INF] Child process started at 03/25/2026 00:27:41

CG  [00:27:41 INF] Compute 9.0.0.0, Rhino 9.0.26083.1000

RC  [00:27:41 INF] Starting compute.geometry instance on port 6004

CG  [00:27:41 INF] Child process started at 03/25/2026 00:27:41

CG  [00:27:41 INF] Compute 9.0.0.0, Rhino 9.0.26083.1000

CG  [00:27:41 INF] Child process started at 03/25/2026 00:27:41

CG  [00:27:41 INF] Compute 9.0.0.0, Rhino 9.0.26083.1000

CG  [00:27:43 INF] Skipping DocumentServer.AddDocument on Linux headless compute

CG  [00:27:43 ERR] Connection id "0HNKA0L9N9GMI", Request id "0HNKA0L9N9GMI:00000001": An unhandled exception was thrown by the application.

System.TypeInitializationException: The type initializer for 'Grasshopper.GUI.Canvas.GH_Canvas' threw an exception.

 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

 ---> System.PlatformNotSupportedException: System.Drawing.Common is not supported on this platform.

   at System.Drawing.Image..ctor()

   at System.Drawing.Bitmap..ctor(Stream stream)

   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)

   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

   --- End of inner exception stack trace ---

   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)

   at System.Resources.Extensions.DeserializingResourceReader.DeserializeObject(Int32 typeIndex)

   at System.Resources.Extensions.DeserializingResourceReader._LoadObjectV2(Int32 pos, ResourceTypeCode& typeCode)

   at System.Resources.Extensions.DeserializingResourceReader.LoadObjectV2(Int32 pos, ResourceTypeCode& typeCode)

   at System.Resources.Extensions.DeserializingResourceReader.LoadObject(Int32 pos, ResourceTypeCode& typeCode)

   at System.Resources.Extensions.RuntimeResourceSet.ReadValue(DeserializingResourceReader reader, Int32 dataPos, Boolean isString, ResourceLocator& locator)

   at System.Resources.Extensions.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase, Boolean isString)

   at System.Resources.Extensions.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase)

   at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture, Boolean wrapUnmanagedMemStream)

   at Grasshopper.My.Resources.Res_GUI.get_Doc_Aborted_48x48() in /scratch/buildAgent/work/db0f3a602490518c/src4/rhino4/Plug-ins/Grasshopper/Grasshopper/Res_GUI.Designer.vb:line 292

   at Grasshopper.GUI.Canvas.GH_Canvas..cctor() in /scratch/buildAgent/work/db0f3a602490518c/src4/rhino4/Plug-ins/Grasshopper/Grasshopper/GH_Canvas.vb:line 3458

   --- End of inner exception stack trace ---

   at Grasshopper.GUI.Canvas.GH_Canvas.get_Canvases() in /scratch/buildAgent/work/db0f3a602490518c/src4/rhino4/Plug-ins/Grasshopper/Grasshopper/GH_Canvas.vb:line 129

   at Grasshopper.Instances.InvalidateCanvas() in /scratch/buildAgent/work/db0f3a602490518c/src4/rhino4/Plug-ins/Grasshopper/Grasshopper/Instances.vb:line 507

   at Grasshopper.Kernel.GH_Document.NewSolution(Boolean expireAllObjects, GH_SolutionMode mode) in /scratch/buildAgent/work/db0f3a602490518c/src4/rhino4/Plug-ins/Grasshopper/Grasshopper/GH_DocumentSolutionMethods.vb:line 311

   at compute.geometry.GrasshopperDefinition.Solve(Int32 rhinoVersion, SchemaDataFormat format)

   at compute.geometry.ResthopperEndpointsModule.GrasshopperSolveHelper(Schema input, String body, Stopwatch stopwatch, HttpContext ctx)

   at compute.geometry.ResthopperEndpointsModule.Grasshopper(HttpContext ctx)

   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

RC  [00:27:43 ERR] HTTP POST /grasshopper responded 500 in 2919.6371 ms

@chris.welch I’m having some success with ScheduleSolution(0) instead of NewSolution() ! But still getting a sense for what might be quietly different.

Edit: Spoke too soon. I don’t think this is working the way I first thought it was.

Coming up a bit dry on alternatives (we are now attempting reflection :melting_face: ) so would like to +1 Chris:

  • Ideally, a way to make GH_Document.NewSolution safe to call on Linux
  • Alternatively, some details on how to use ScheduleSolution(0) on Linux, if possible. (I can’t get any document solution events to fire, but am totally guessing at how this is supposed to work.)
1 Like

Yeah, I’m calling GH blocked for now until there’s a fix for this custom System.Drawing that McNeel manage - @stevebaer, that’s over the fence to you guys I think. Very excited about this progress, but not enough to keep hacking around the edges

@chris.welch @Chuck_Driesler Can you provide a small cs program that demonstrates the behavior you are seeing?

FWIW, this pattern works for me. I like it as it solves only the components that need solving.

//this assumes your definition uses Context Print components.
string filePath = "./mydopedef.gh";
var io = new Grasshopper.Kernel.GH_DocumentIO();
if (!io.Open(filePath))
  Console.WriteLine("File loading failed.");
else
{
  var doc = io.Document;
  doc.Enabled = true;
  foreach (var obj in doc.Objects)
  {
    Type objectClass = obj.GetType();
    var className = objectClass.Name;
    if (className == "ContextPrintComponent")
    {
      var contextPrinter = obj as Grasshopper.Kernel.GH_Component;
      if (contextPrinter != null && !contextPrinter.Locked)
      {
        Grasshopper.Kernel.IGH_Param param = contextPrinter.Params.Input[0];
        param.CollectData();
        param.ComputeData();
        foreach (var item in param.VolatileData.AllData(true))
          Console.WriteLine($"{param.NickName}: {item}");
      }
    }
  }
}
1 Like

This looks like it will work! I got tunnel vision on component-level solve and forgot we had this on params.

I will also provide a small repro, but it’s literally any call to .NewSolution() on any doc (even in your snippet here, after doc.Enabled.

Can confirm this is working @fraguada ! hot hot hot

more soon (:

1 Like

Managing to solve Grasshopper definitions on Linux (very cool). Sometimes certain components will hard crash the server (pain). It’s the same error we were reporting at the start of the thread: the kind of thing that hits some low level code and kills the process (even within a try/catch).

To help with debugging, I’ve forked and modified the rhino compute repo here:

It adds a /debug/:fileName endpoint to the compute.geometry project that will load and try to solve a small diagnostic grasshopper script. (GET http://localhost:7777/debug/000-control.gh should work). It also has a devcontainer there that replicates my WSL environment.

Please take a swing and let me know if it helps clarify the nature of the crashes at all! (And anything else I can add to help with testing.) I’ll keep dropping cases in there and reporting here.

tldr; it feels like any time a surface gets involved, things complain. The cases here are a simple extrusion or a circle, or passing it into the Boundary component.

1 Like

@chris.welch It would be good to understand what you are trying to do. I worked with @Chuck_Driesler today to see what he was trying to set up. He was trying to run his own slightly modified version of Rhino.Compute, built from source. In this case, one needs to export the environment variables explicitly, for example, by adding them to ~/.profile and then running source ~./profile

The steps we took:

sudo nano ~/.profile
# add this to the bottom of the file 
export RHINO_TOKEN=<your-token-here>
export RHINO_COMPUTE_CREATE_HEADLESS_DOC=true
# save and close the file
source ~./profile
# run dotnet run from the rhino.compute src directory

But that worked for his situation. It would be good to know a bit more about what you are doing to understand where things are going wrong.

1 Like