Error when calling karamba functions with out parameters in python3 via pythonnet

Hi all,

I am trying to follow the ModelCreation.gh provided in the scripting guide examples to build a Karamba model and solve in a CPython environment.

So far, I’ve succeeded in converting the provided C# example to IronPython as below:

Since IronPython does not support python3.X and I want to use Karamba in an external CPython environment, I am trying to use the clr provided by pythonnet and reproduce what we can do with IronPython. To reproduce the error below, you can use any python3.x distribution from CPython (not in Rhino/GH) and install pythonnet by running pip install pythonnet.

A minimal example is attached at the end of this post. All the module importing works, and the construction of Karamba.Geometry.Point3 and Line3 works. However, when it comes to LineToBeam, which has the following C# function definition:

List<BuilderBeam> LineToBeam(
	List<Line3> curves,
	List<string> identifiers,
	List<CroSec> crosecs,
	MessageLogger info,
	out List<Point3> outNodes,
        // optional params...
        )

In IronPython, we can use clr.Reference[Type]() to construct the variables for out and it works well (read more about this in IronPython’s doc). But for pythonet, we do not have such a mechanism. Instead, the pythonnet documentation and test code here suggests placing a None or a placeholder dumb object in the out parameter. However, I get the following error when passing a dumb placeholder object for outNodes when using pythonnet:

nodes = List[Point3]()
# this doesnt' work either
# nodes = List[Point3]().GetType().MakeByRefType ()
elems, ns = k3d.Part.LineToBeam(List[Line3]([L0]), List[str](["B1"]), List[CroSec](), 
    logger, nodes)

Error messages:

System.EntryPointNotFoundException: Unable to find an entry point named 'SWIGRegisterExceptionCallbacks_karamba' in DLL 'karamba'.
   at feb.karambaPINVOKE.SWIGExceptionHelper.SWIGRegisterExceptionCallbacks_karamba(ExceptionDelegate applicationDelegate, ExceptionDelegate arithmeticDelegate, ExceptionDelegate divideByZeroDelegate, ExceptionDelegate indexOutOfRangeDelegate, ExceptionDelegate invalidCastDelegate, ExceptionDelegate invalidOperationDelegate, ExceptionDelegate ioDelegate, ExceptionDelegate nullReferenceDelegate, ExceptionDelegate outOfMemoryDelegate, ExceptionDelegate overflowDelegate, ExceptionDelegate systemExceptionDelegate)
   at feb.karambaPINVOKE.SWIGExceptionHelper..cctor()

The above exception was the direct cause of the following exception:

System.TypeInitializationException: The type initializer for 'SWIGExceptionHelper' threw an exception.
   at feb.karambaPINVOKE.SWIGExceptionHelper..ctor()
   at feb.karambaPINVOKE..cctor()

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".\model_creation.py", line 59, in <module>
    logger, nodes)
System.TypeInitializationException: The type initializer for 'feb.karambaPINVOKE' threw an exception.
   at feb.karambaPINVOKE.new_NKDTreeDupli__SWIG_0(Int32 jarg1)
   at feb.NKDTreeDupli..ctor(Int32 number_of_points)
   at Karamba.Elements.LineToBeam.solve(List`1 in_nodes, List`1 in_curves, Boolean new_nodes, Boolean remove_dup, Double limit_dist, List`1 in_z_oris, List`1 in_ids, List`1 in_colours, List`1 in_crosecs, Boolean in_bending, List`1& out_points, List`1& out_beams, String& info)
   at KarambaCommon.Factories.FactoryPart.LineToBeam(List`1 curves, List`1 identifiers, List`1 crosecs, MessageLogger info, List`1& outNodes, Boolean bending, Double limitDist, List`1 zOris, List`1 colors, List`1 points, Boolean newNodes, Boolean removeDup)

Before diving into pythonnet and debug there, is there useful diagnosis information we can deduce from the messages above?

A code snippet of a minimal testing example is attached below. The full python code and GH script can be found here: pythonnet_examples.zip (725.9 KB)

Thank you so much in advance for helping!

Best,
Yijiang

##############################

import clr
import sys
import os

ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
BIN_DIR = os.path.join(ROOT_DIR, 'bin')
sys.path.append(BIN_DIR)
clr.AddReference("Karamba")
clr.AddReference("KarambaCommon")
clr.AddReference("System.Collections")
clr.AddReference("System.Runtime")

import Karamba
import feb 
from Karamba.Models import Model
from Karamba.Geometry import Point3, Line3, Vector3
from Karamba.CrossSections import CroSec
from Karamba.Supports import Support
from Karamba.Loads import Load
from Karamba.Utilities import MessageLogger, UnitsConversionFactories
import KarambaCommon

import System
from System.Collections.Generic import List

###############################

logger = MessageLogger();
k3d = KarambaCommon.Toolkit();

p0 = Point3(0.0, 0.0, 0.0);
p1 = Point3(0.0, 0.0, 5.0);
L0 = Line3(p0, p1);

# IronPython clr trick to handle C# out parameter
# https://ironpython.net/documentation/dotnet/dotnet.html#id52
# nodes = clr.Reference[List[Point3]]()

# pythonnet
# https://mail.python.org/pipermail/pythondotnet/2010-November/001011.html
nodes = List[Point3]()
# nodes = List[Point3]().GetType().MakeByRefType ()

elems, ns = k3d.Part.LineToBeam(List[Line3]([L0]), List[str](["B1"]), List[CroSec](), 
    logger, nodes)

BTW, running the script that works in Rhino’s IronPython does not work when run in an IronPython distribution outside Rhino. The complete script is attached at the end of this post.

The error happens in this line:

elems = k3d.Part.LineToBeam(List[Line3]([L0]), List[str](["B1"]), 
    List[CroSec](), logger, nodes);

and the error message is:

Traceback (most recent call last):
  File ".\ipy_model_creation.py", line 47, in <module>
SystemError: The type initializer for 'feb.karambaPINVOKE' threw an exception.

Can Karamba developers give me some hint on what went wrong here, since we do no have access to the C++ module feb's documentation?

##############################

import clr
import os
import sys
# https://ironpython.net/documentation/dotnet/dotnet.html

# clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 6\Plug-ins\Karamba.gha")
# clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 6\Plug-ins\KarambaCommon.dll")
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
BIN_DIR = os.path.join(ROOT_DIR, 'bin')
sys.path.append(BIN_DIR)
clr.AddReference("Karamba")
clr.AddReference("KarambaCommon")

import feb # Karamba's C++ library (undocumented in the API)
import Karamba.Models.Model as Model
# import Karamba.GHopper.Models.GH_Model as GH_Model
#
import Karamba
import Karamba.Elements.ModelTruss as Truss
from Karamba.Geometry import Point3, Line3, Vector3
from Karamba.CrossSections import CroSec
from Karamba.Supports import Support
from Karamba.Loads import Load
#
from Karamba.Utilities import MessageLogger, UnitsConversionFactories
import KarambaCommon
#
import System
from System import GC
from System.Collections.Generic import List

###############################

logger = MessageLogger();
k3d = KarambaCommon.Toolkit();

p0 = Point3(0, 0, 0);
p1 = Point3(0, 0, 5);
L0 = Line3(p0, p1);

# clr trick to handle C# out parameter
# https://ironpython.net/documentation/dotnet/dotnet.html#id52
nodes = clr.Reference[List[Point3]]()
print(type(nodes))
elems = k3d.Part.LineToBeam(List[Line3]([L0]), List[str](["B1"]), 
    List[CroSec](), logger, nodes);

cond = List[System.Boolean]([True for _ in range(6)]);
support = k3d.Support.Support(0, cond);
supports = List[Support]([support]);

pload = k3d.Load.PointLoad(1, Vector3(0, 0, -10), Vector3());
ploads = List[Load]([pload]);

mass = clr.Reference[float]()
cog = clr.Reference[Point3]()
warning_flag = clr.Reference[bool]()
info = clr.Reference[str]()
msg = clr.Reference[str]()
model = k3d.Model.AssembleModel(elems, supports, ploads,
    info, mass, cog, msg, warning_flag);

# calculate Th.I response
max_disp = clr.Reference[List[float]]()
out_g = clr.Reference[List[float]]()
out_comp = clr.Reference[List[float]]()
message = clr.Reference[str]()
model = k3d.Algorithms.AnalyzeThI(model, max_disp, out_g, out_comp, message);

ucf = UnitsConversionFactories.Conv();
cm = ucf.cm();
print "max disp: {} {}".format(cm.toUnit(max_disp.Value[0]), cm.unitB)