# Welcome to Karamba3D Scripting Guide

The official scripting guide using Karamba3D 3.1.4

**Using Karamba3D in C# Scripts**

This manual provides guidance on utilizing Karamba3D within C# scripts. As C# is part of the .NET ecosystem, the information presented here also applies to other .NET-based scripting languages, such as IronPython, F#, Visual Basic, or any language built on the Common Language Infrastructure (CLI).

**Python Support in Karamba3D**

* **IronPython Examples**: [Section 2.7](/2.-scripting-with-karamba3d-inside-grasshopper/2.7-the-ironpython-component) includes example scripts written in IronPython. While IronPython is compatible with the .NET framework, its utility is limited due to a smaller selection of available libraries.
* **CPython in Rhino 8**: With Rhino 8, it is now possible to embed CPython scripts in Grasshopper definitions. CPython supports a comprehensive set of libraries, making it significantly more versatile than IronPython. Examples leveraging Grasshopper's new **"Python 3 Component"** are provided in [Section 2.8](/2.-scripting-with-karamba3d-inside-grasshopper/2.8-the-python-3-component).

**Scope of the Manual**

This manual does not serve as an introduction to C# or Python. For beginners, refer to resources such as [\[1\]](/bibliography) or [\[3\]](/bibliography). For advanced topics, which are not essential for understanding this manual but may be beneficial, see the highly regarded **“C# in Depth”** [\[4\]](/bibliography).

{% hint style="info" %}
Examples detailed in this guide can be found in the "[Karamba3D Scripting Examples Github](https://github.com/karamba3d/K3D_Scripting)"-collection.
{% endhint %}

## Citing Karamba3D

In case you use Karamba3D for your scientific work, please cite the following paper:

> Preisinger, C. (2013), *Linking Structure and Parametric Geometry*. Architectural Design, 83: 110-113\
> DOI: 10.1002/ad.1564.

## Disclaimer

Although being tested thoroughly Karamba3D probably contains errors – therefore no guarantee can be given that Karamba3D computes correct results. Use of Karamba3D is entirely at your own risk. Please read the [license agreement](https://www.karamba3d.com/buy/license-agreement/) that comes with Karamba3D in case of further questions.

This manual is written by Clemens Preisinger.\
Editing by Georg Lobe.


# 1.1: Scripting with Karamba3D

Sometimes when creating a Grasshopper (GH) definition you may reach a point where it makes sense to switch from GH’s visual computing environment to textual scripting. This is e.g. the case if you want to apply loops, functions or more refined object oriented programming concepts. Other points in favor of a textual approach would be debugging or code reuse.

When installed for Grasshopper Karamba3D consists of three main parts:

* **“karamba.dll”** is a C++ library which does the numeric calculations.&#x20;
* **“karambaCommon.dll”** provides the .NET user interface to the Karamba3D functionality. It features its own set of geometric types (e.g. vectors, points, meshes,. . . ) and is therefore independent from Grasshopper or Rhino.&#x20;
* **“karamba.gha”** connects GH to **“karambaCommon.dll”** and takes care of the graphical user- interface.&#x20;

These files reside in the installation folder of Karamba3D. Their location depends on the chosen installation method:

* **Installation via MSI Installer**\
  Files are located in Rhino's “Plug-ins” folder. The default path is:\
  `C:\Program Files\Rhino 8\Plug-ins\Karamba\`
* **Installation via Package Manager (YAK)**\
  Files are installed in the following folder:\
  `C:\Users\<YourUsername>\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Karamba3D\3.1.40918`\
  \&#xNAN;*(Example shown for version 3.1.40918)*\
  To access this folder quickly, use the file explorer and enter `%appdata%` to navigate to the "Roaming" folder.

Alternatively, the Karamba3D NuGet package can be used to integrate the functionality of **karambaCommon** (refer to documentation \[[here](https://www.nuget.org/packages/KarambaCommon/)]). This method is utilized in the [**K3D\_tests**](https://github.com/karamba3d/K3D_tests) project.

**API Documentation and Additional Resources**

* **API Documentation**: The Karamba3D API documentation is available at <https://www.karamba3d.com/help/3-1-4>.
* **Exploring Code**: The `karambaCommon.dll` and `karamba.gha` files can be inspected using tools like “ILSpy” to decompile and analyze their functionality.

**Purpose of Decoupling from Grasshopper**

Decoupling Karamba3D (K3D) from Grasshopper was primarily aimed at enabling unit testing for its C# application programming interface (API) to improve code quality.

* **Test Project**: The Karamba3D test project can be downloaded from <https://github.com/karamba3d/K3D_tests>.\
  The test cases include code snippets demonstrating API usage, serving as a valuable learning resource for working with the Karamba3D C# API.

**Notes on Development and Interface Stability**

Karamba3D is under active development, and its interface definitions are subject to change in future releases. Users should account for potential updates when working with the API.


# 1.2: Basics

## Independence from Grasshopper

Karamba3D can operate independently of Grasshopper and Rhino. The [API documentation](https://www.karamba3d.com/help/3-1-4/) provides an overview of its namespaces:

* **Namespaces under `Karamba.GHopper`**\
  These are linked to Grasshopper functionality and are contained within the `karamba.gha` file. They connect Karamba3D’s C# API to Grasshopper.
* **Other Namespaces**\
  All remaining namespaces reside within the `karambaCommon.dll` file and can be utilized without relying on Grasshopper or `RhinoCommon.dll`. This allows for standalone use of Karamba3D’s core functionalities.

## Karamba3D's Geometry Types

The `Karamba.Geometry` namespace defines the geometry types used in Karamba3D. These types generally mirror Grasshopper's geometry types but without the "D" suffix. For example:

* Grasshopper’s `Vector3D` corresponds to Karamba3D’s `Vector3`.

**Type Conversion Utilities**

* **`Karamba.GHopper.Utilities.FromGH`**\
  This static class provides methods for converting Grasshopper-wrapped types (e.g., `GH_Vector`) to Karamba3D types.
* **`Karamba.GHopper.Utilities.ToGH`**\
  This static class provides methods for converting from Karamba3D to Grasshopper-wrapped types.
* **`Karamba.GHopper.Geometry` Namespace**\
  This namespace includes the `ToKarambaCommon` and `ToRhino` classes. Thes extends Grasshopper and Karamba3D geometry classes with the `Convert` method. This method enables bi-directional type conversion between Grasshopper and Karamba3D geometry types.

## Managing Karamba3D Models: Static Classes and Factories

Most Karamba3D components visible in Grasshopper are backed by static classes within `karambaCommon.dll`. These static classes perform the core computations. While it is possible to manage Karamba3D models directly using these classes, this approach ties scripts to their specific interface definitions, which may change over time.

**Factory-Based Approach for Stability**

To provide a more stable API, a factory hierarchy has been implemented under the `KarambaCommon.Factories` namespace. This approach is recommended for setting up and managing Karamba3D models.

* **Access via the `Toolkit` Class**\
  The `Toolkit` class serves as an entry point to the factory system, offering a more robust and user-friendly method to create and manage Karamba3D models. It also provides sensible default values for many arguments, simplifying setup.

**Current and Future Features**

* Not all Karamba3D features are currently accessible through the `Toolkit`.
* The `Toolkit` will be expanded in future versions and is the preferred method for interacting with Karamba3D.

**Learning Resources**

For examples of how to use the `Toolkit` and factory system, refer to the Karamba3D unit-test project:\
<https://github.com/karamba3d/K3D_tests>.\
These test cases demonstrate practical usage of the API and provide valuable insights for developers.


# 2.1: Hello Karamba3D

For small scripts the Grasshopper scripting components “C# Script”, “VB Script” or “Python Script” from the **“Maths”** subsection come in handy. A good introduction to scripting in GH can be found in the “Grasshopper Primer”.

All examples which follow can be found in the “[Karamba3D Scripting Examples Github](https://github.com/karamba3d/K3D_Scripting)”-collection that accompanies this manual. When opening them in Grasshopper do not panic if some components turn red. In that case some paths need adaptation (see below).

### Setting up a Script in Rhino6/7

To begin using Karamba3D in a Grasshopper script, follow this example (refer to *“HelloKaramba3D\_RH7.gh”* linked at the bottom of the relevant documentation page):

1. **Add a C# Scripting Component**
   * Place a C# scripting component onto the Grasshopper canvas.
2. **Manage Assemblies**
   * Right-click on the component's icon and select **"Manage Assemblies..."** from the context menu.
3. **Add Karamba3D Assemblies**
   * Under **"Referenced Assemblies,"** click **"Add."**
   * Browse to locate the `karambaCommon.dll` and `karamba.gha` files. These files are typically located in the Rhino Plug-ins folder:\
     `C:\Program Files\Rhino7\Plug-ins\Karamba3D`
   * Ensure you select the appropriate file type:
     * By default, only `.dll` files are visible.
     * Use the drop-down menu in the lower-right corner of the file browser to select **"Grasshopper Assemblies (\*.gha)"** for the `.gha` file.

This setup makes the Karamba3D assemblies accessible to your script, enabling full integration with the plug-in.

### Setting up a Script in Rhino8

The Rhino 8 script editor for C# introduces significant improvements over its predecessor, including built-in debug support. To reference external assemblies, you can explicitly include them in your code.

**Adding Karamba3D Assemblies**

For example, the following lines reference the required Karamba3D assemblies (see *"HelloKaramba3D\_RH8.gh"* at the bottom of this page):

````
#r "C:\Users\<YourUsername>\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Karamba3D\3.1.40918\KarambaCommon.dll"
#r "C:\Users\<YourUsername>\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Karamba3D\3.1.40918\Karamba.gha"```
````

* Replace `<YourUsername>` with your specific user name to match your installation path.
* The files are located in the personal folder if Karamba3D was installed via the YAK package manager.

**Convenient Assembly Selection**

For a more user-friendly approach to referencing assemblies:

1. Use the **"Box" button** in the script editor toolbar to open the assembly selection browser.
2. Browse and add the required files directly.

By following these steps, you can integrate Karamba3D assemblies into your Rhino 8 C# scripts with ease.

### Explanation of Code

![Fig. 2.1.1: A minimal K3D-model for retrieving the number of elements, materials and cross sections.](/files/-MXH_8qbowH6_g1a2jDZ)

The source-code to be added inside the C#-component looks like this:

{% code lineNumbers="true" %}

```csharp
    ...
    using Karamba.Models;
    ...
    private void RunScript(object Model_in)
    {
        var model = Model_in as Model;    
        if (model == null) {
            throw new ArgumentException("The input is not of type model!");
      }
      Print("Number of Elements: " + model.elems.Count);
      Print("Number of Materials: " + model.materials.Count);
      Print("Number of Cross sections: " + model.crosecs.Count);
    }
```

{% endcode %}

* **Namespace Inclusion**:\
  The `using` directive (`using Karamba.Models;`) allows shorter, more readable code. For example, instead of typing `Karamba.Models.Model`, you can simply use `Model`.
* **Casting the Input**:
  * `Model_in` is provided as an `object`.
  * To access its properties, it must be cast to the correct type (`Model`).
  * The `if` condition checks if the casting was successful. If not, an `ArgumentException` is thrown to handle invalid inputs (e.g., a vector plugged into the model input).
* **Accessing the Model**:
  * Once cast, the `model` variable holds a reference to the Karamba3D model.
  * The script retrieves data about the model, such as the count of elements, materials, and cross-sections.
* **Output Details**:
  * Use `Print` to display the results in the Grasshopper console.
  * Example outputs:
    * *Number of Elements: 1*
    * *Number of Materials: 1*
    * *Number of Cross sections: 1*

**API Documentation**

For detailed information about the `Model` class and its API, visit the [Karamba3D API Documentation](https://www.karamba3d.com/help/3-1-4), specifically under `Karamba.Models`.

{% file src="/files/szwGosPn3XdS2CKZ2kAE" %}

{% file src="/files/L6LeGItXaWyfdcAOuD2L" %}


# 2.2: Data Retrieval from Models

## The Data Model

The data inside a Karamba3D model is organized in a tree-like object structure. The following diagram shows a part of that tree – for full details see the [Karamba3D API documentation](https://www.karamba3d.com/help/3-1-4/html/b2fe4d67-e7e2-4f96-bc84-ecd423bde1a7.htm):

![](/files/-MXH_8cizq26hoYB9NSc)

## Retrieving Masses Sorted by Material

The following script (see “**DataRetrieval.gh**”) shows how to use that model-data to retrieve the mass sorted by material. One can see in fig 2.2.1 the corresponding GH setup. The model comprises two beams made from steel and concrete respectively. The total mass amounts to **87.5 kg**, **37.5 kg** come from concrete, **50.0 kg** from steel.

![Figure 2.2.1: Data retrieval from a K3D model.](/files/-MXH_8ckB3AWFBtvo-zl)

{% code lineNumbers="true" %}

```csharp
...
using Karamba.Models;
using Karamba.Utilities;
using Karamba.Materials;
...
private void RunScript(object Model_in)
{
  var model = Model_in as Model;
  if (model == null) {
    throw new ArgumentException("The input is not of type Karamba.Models.Model!");
  }

  var matWeights = new Dictionary<FemMaterial, double>();

  foreach (var elem in model.elems) {
    var mat = elem.crosec.material;
    if (!matWeights.ContainsKey(mat)){
      matWeights[mat] = 0;
    }
    matWeights[mat] += elem.weight(model.nodes);
  }

  UnitsConversionFactory ucf = UnitsConversionFactory.Conv();
  var mass = ucf.force2mass();
  var kg = ucf.kg();

  foreach (var entry in matWeights) {
    Print("Material: " + entry.Key.name + ": " + kg.toUnit(mass.toBase(entry.Value)) + kg.unitB);
  }
}
```

{% endcode %}

The first lines contain **“using”** statements for the name-spaces **“Karamba.Utilities”** and **“Karamba.Materials”**. These house the **“UnitsConversionFactories”**, **“INIReader”** and **“FemMaterial”**-classes respectively.

The script starts as before with a type-conversion for the argument **“Model\_in”** from **“object”** to **“Model”**. The **“matWeights”** dictionary provides the mapping from K3D materials to weights. A **“foreach”**-loop cycles over all elements of the model and gets their materials. If not already present in **“matWeights”** a new material entry is created. Line 20 updates the material’s total weight.

{% file src="/files/LCZKB9Hy9bk7zJwFE9IC" %}

{% file src="/files/RWBYpGz3aAQ6ZiZLVMm5" %}

## Handling Physical Units

Creating the output consists of looping over the entries in **“matWeights”**. The tricky part is to get the physical units right. Internally the C++ part of Karamba3D does not care about physical units. As long as they are consistent the results will be fine. When using SI-units Karamba3D works with these base units: meters (**m**), kilo Newtons (**kN**), tons (**t**) and degrees Celsius. When in Imperial-mode the units of length, force and mass get converted to feet (**ft**), kilo Pounds force(**kipf**), kilo Pounds mass (**kipm**) and degrees Fahrenheit. Units conversion between e.g. centimeter and meter, inch and feet, . . . occurs at output and input only. The material and cross section tables which come with Karamba3D – and can be produced via e.g. the **“Generate Cross Section Table”**-component – contain values in SI-units only. They get converted to the right units-system (SI or Imperial) on the fly when loading them into Karamba3D.

The matter of mass has it difficulties: in the SI system there is a clear separation between force (**kN**) and mass (**kg**) and e.g. Newton’s law takes the form:

$$
F\[N] = m\[kg] \* a\[m/s^2]
$$

since the definition holds:

$$
1N = 1kgm/s^2
$$

In Imperial units one has Pound-force (**lb**, sometimes **lbf**) and Pound-mass (**lbm**). The former is defined as the force which corresponds to the weight of one pound mass. So **“g”** – the acceleration of gravity – is not involved. To make Newton’s law work in Imperial units one thus needs to divide the right side by a constant:

$$
g\_c = 32.174 ft/s^2
$$

which is by convention the acceleration of gravity to be used:

$$
F \[lbf] = m\[lbm] · a\[ft/s^2]/g\_c \[ft/s^2]
$$

To make calculations involving mass work irrespective of the system of physical units the **“kg”**- conversion does the following: Under Imperial units masses get scaled by:

$$
1/g\_c
$$

Sometime it happens that one wants to convert weight to mass. In this case weight gets scales by *g\_user/g\_c* and *g\_user* for Imperial and SI-units respectively, g\_user being the acceleration of gravity given in the karamba.ini-file.

The first step in unit-conversion consists of getting a units-conversion-factory (UCF), (see line 23). This factory converts derived SI or Imperial units (e.g. inch, centimeter, millimeter, . . . ) to base units (e.g. feet, meter). A UCF features a long list of conversion objects, “**weight2kg**” being one of them. In line 24 a units conversion object gets instantiated using the factory. This lets one convert from force to mass using the acceleration of gravity from the “karamba.ini”-file via the “**toBase**”-method. Conversion in the other direction works with “**toUnit**”. The method “**unitB**” renders a string representation of the unit with brackets.


# 2.3: How to Create Structural Models

Karamba3D-components are split in two parts: one manages the graphical user interface, unit conversions and default values, the other handles the functionality.

Let’s take the **“LineToBeam”**-component as an example:

* The class **“Component\_LineToBeam\_GUI”** in namespace **“Karamba.GHopper.Elements”** derives from Grasshopper’s **“GH\_Component”** and provides the visual component properties.
* **“LineToBeam”,** a static class in namespace **“Karamba.Elements”** features the static method **“solve(...)”** which executes the actual tasks and gets used by **“Component\_LineToBeam\_GUI”**.

Generally the names of classes which belong to the GUI start with **“Component”** and belong to the namespace **“Karamba.GHopper”**. Since there are static solve-methods for all components it would be possible to build a model using only these. This would entail two disadvantages:

* The solve-methods do not provide default values for their arguments, so one has to provide them explicitly.
* In later versions of Karamba3D the order and number of arguments of the solve-methods might change.

One way to mitigate these problems is to refrain from direct object creation and use a factory-pattern instead. See [\[2\]](/bibliography) for further information on this topic. In the script below a structural model gets assembled and output: it consists of a vertical cantilever-beam with a point-load on top (see fig. 2.3.1).

![Fig. 2.3.1: A model can be created from scratch using a C# script.](/files/-MXH_8YK8Xgq4TshBhlr)

This is the source-code inside the C#-component (see example “**ModelCreation.gh**”):

{% code lineNumbers="true" %}

```csharp
...
using Karamba.Utilities;
using Karamba.Geometry;
using Karamba.CrossSections;
using Karamba.Supports;
using Karamba.Loads;
...
private void RunScript(ref object Model_out)
{
  var logger = new MessageLogger();
  var k3d = new KarambaCommon.Toolkit();

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

  var nodes = new List<Point3>();

  var elems = k3d.Part.LineToBeam(new List<Line3>(){L0}, new List<string>(){ "B1" },
    new List<CroSec>(), logger, out nodes);

  var cond = new List<bool>(){ true, true, true, true, true, true};
  var support = k3d.Support.Support(0, cond);
  var supports = new List<Support>(){support};

  var pload = k3d.Load.PointLoad(1, new Vector3(0, 0, -10), new Vector3());
  var ploads = new List<Load>(){pload};

  double mass;
  Point3 cog;
  bool flag;
  string info;
  var model = k3d.Model.AssembleModel(elems, supports, ploads,
    out info, out mass, out cog, out info, out flag);

  // calculate Th.I response
  IReadOnlyList<double> max_disp;
  IReadOnlyList<Vector3> out_force;
  IReadOnlyList<double> out_energy;
  string warning;
  model = k3d.Algorithms.Analyze(model, new List<string>(){"LC0"}, out max_disp, out out_force, out out_energy, out warning);

  var ucf = UnitsConversionFactories.Conv();
  UnitConversion cm = ucf.cm();
  Print("max disp: " + cm.toUnit(max_disp[0]) + cm.unitB);

  Model_out = new Karamba.GHopper.Models.GH_Model(model);
}
```

{% endcode %}

As a means of reporting problems a **“logger”**-objects gets instantiated in line 10. This class limits the amount of text to a preset maximum so that in case of multiple errors the log-file does not grow without limits. Next comes the factory **“k3d”** which further on serves as the main hub of object creation. The classes **“Point3”** and **“Line3”** represent the Karamba3D equivalent of Grasshopper’s **“Point3d”** and **“Line”**. Their instantiations **“p0”**, **“p1”** and **“L0”** make up the model’s geometry. In line 19 the **k3d-factory** creates a list of objects of type **“BuilderBeam”** – with one entry in this case. This is not yet an element which forms part of a model – this would be **“ModelBeam”**. It rather represents a recipe for creating them. This concept applies to all elements in Karamba3D: Via the assemble-step objects of type **“BuilderBeam”** or **“BuilderShell”** produce **“ModelBeams”**-, **“ModelTruss”**-, **“ModelSpring”** and **“ModelShell”**-objects which form part of the C# structural model. What the user sees as **“Element”** in the Grasshopper GUI are the element-builders not the model-elements. Since C# structural models can be disassembled and reassembled the model-elements need to keep a reference to their builder-elements. This is achieved via the protected property **“builder\_element”**.

The creation of supports and loads works similarly as for the element-builder. In Line 33 follows the model-assemble step, in line 41 the first order theory calculation of the model-response.

User defined objects that get piped through grasshopper definitions need to be wrapped: In line 47 a GH\_Model wrapper object is created from the model-object that contains the final results. Similar wrapper classes exist for all Karamba3D entities that can populate a Grasshopper definition. Their names start with **“GH\_”** which makes them easy to find.

{% file src="/files/mkZsW7QnFDZjpuIgqHtq" %}

{% file src="/files/W1i8jI8IdivSiyxPofeM" %}


# 2.4: How to Modify Structural Models

{% content-ref url="/pages/-MXH\_8OKtO9W8PyR39vI" %}
[2.4.1: Cross section Optimization](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.1-cross-section-optimization)
{% endcontent-ref %}

{% content-ref url="/pages/-MXH\_8OLycjhQRsWG1uV" %}
[2.4.2: Activation and Deactivation of Elements](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements)
{% endcontent-ref %}


# 2.4.1: Cross section Optimization

When it comes to modifying an existing Karamba3D model keep to these general rules:

* In order to avoid side-effects, clone objects before modifying them. This applies recursively to the objects which contain objects to be modified.
* The Karamba3D API for modifications allows for changes which can make the C++-model crash. So make it a habit to regularly save your work.

The example “**CrossSectionOptimization.gh**” (see fig. 2.4.1.1) shows how to optimize beam cross sections under arbitrary loads. The C#-component takes a model and an ordered list of cross sections as the main input, chooses the optimum cross sections according to the given cross section forces and outputs the optimized model as well as its maximum displacement. The input-plug **“niter”** lets one chose the number of iteration steps. Each consists of model evaluation and cross section selection. With **“lcName”** the name of a load-case to be considered for cross section optimization can be selected. The algorithm inside the component neglects buckling and calculates the maximum stress in the cross section based on the assumption that there are only normal forces and bending moments about the local Y-axis.

<figure><img src="/files/x60K0MA55CgQlmV7OeWJ" alt=""><figcaption><p>Fig. 2.4.1.1: Modification of cross sections.</p></figcaption></figure>

{% code lineNumbers="true" %}

```csharp
...
using Karamba.Models;
using Karamba.CrossSections;
using Karamba.Elements;
using Karamba.Results;
using Karamba.Geometry;
...
private void RunScript(
	object Model_in,
	List<object> CroSecs_in,
	int niter,
	string lcName,
	ref object Model_out,
	ref object Disp_out)
    {
        var model = Model_in as Model;
        if (model == null) {
          throw new ArgumentException("The input in 'Model_in' is not of type Karamba.Models.Model!");
        }

        var crosecs = new List<CroSec_Beam>(CroSecs_in.Count);
        foreach (var item in CroSecs_in) {
            var crosec = item as CroSec_Beam;
            if (crosec == null) {
                throw new ArgumentException("The input in 'CroSecs_in' contains objects which are not of type Karamba.CrossSections.CroSec_Beam!");
            }
            crosecs.Add(crosec);
        }

        var k3d = new KarambaCommon.Toolkit();
        IReadOnlyList<double> max_disp;
        IReadOnlyList<Vector3> out_force;
        IReadOnlyList<double> out_energy;
        string warning;
        List<List<double>> N;
        List<List<double>> V;
        List<List<double>> M;

        // avoid side effects
        model = model.Clone();
        model.cloneElements();

        for (int i = 0; i < niter; ++i) 
        {
            model = k3d.Algorithms.Analyze(model, new List<string>(){lcName}, out max_disp, out out_force, out out_energy, out warning);

            for (int elem_ind = 0; elem_ind < model.elems.Count; ++elem_ind) {
                var beam = model.elems[elem_ind] as ModelBeam;
                if (beam == null) continue;

                // avoid side effects
                beam = (ModelBeam) beam.Clone();
                model.elems[elem_ind] = beam;

                BeamResultantForces.solve(model, new List<string> {"" + elem_ind}, lcName, 100, 1,
                    out N, out V, out M);

                for (int crosec_ind = 0; crosec_ind < crosecs.Count; ++crosec_ind) {
                    var crosec = crosecs[crosec_ind];
                    beam.crosec = crosec;
                    var max_sigma = Math.Abs(N[0][0]) / crosec.A + M[0][0] / crosec.Wely_z_pos;
                    if (max_sigma < crosec.material.ft()) break;
                }
            }

            model.initMaterialCroSecLists();
            model.buildFEModel();
        }

        model = k3d.Algorithms.Analyze(model, new List<string>(){lcName}, out max_disp, out out_force, out out_energy, out warning);

        Disp_out = new GH_Number(max_disp[0]);

        Model_out = new Karamba.GHopper.Models.GH_Model(model);
    }

```

{% endcode %}

Here the detailed account of the above algorithm:

**1. Input Type Validation**

* The first two blocks of code check the data types of the `Model_in` and `CroSecs_in` inputs. This ensures the inputs are valid and compatible with subsequent operations.

**2. Object Instantiations**

* The third code section initializes objects that are required later during the iterative optimization loop. These objects facilitate efficient data handling and manipulation.

**3. Reference Management in C#:**

* In C#, assigning an object to a variable actually assigns a **reference**. This means changes made to the object are reflected in all variables that reference it.
* This behavior conflicts with Grasshopper’s data flow logic, where objects should only be influenced by upstream operations.
* To prevent unintended data flow and ensure independent object manipulation:
  * Clone the model using `model = model.Clone();`.
  * Clone the model's elements with `model.cloneElements();`.

**4. Optimization Loop**

* The main optimization loop performs the following steps:
  1. **System Response Calculation:**
     * At the start of each iteration, the structural response is calculated.
  2. **Beam Element Handling:**
     * All elements in the model are checked to identify beam elements.
     * Beam elements are cloned to avoid side effects and re-added to the model.
  3. **Cross Section Forces Calculation:**
     * The resultant forces for each beam’s cross section are determined using `BeamResultantForces.solve`.
     * Alternatively, `Karamba.Results.BeamForces.solve()` could be used for more complex scenarios, such as considering bi-axial bending.
  4. **Cross Section Selection:**
     * The loop iterates through the user-provided list of cross sections.
     * An appropriate cross section is selected by comparing the maximum stress in the cross section with the material's strength.
  5. **Model Update:**
     * After selecting cross sections, the model's lists of cross sections and materials are re-initialized.
     * The C++ model, which the C# model relies on for result evaluation, is recreated.

**5. Finalization**

* Before the model is passed to the output plug:
  * An analysis step updates the structural response.
  * The maximum displacement of the model is determined.

**6. Potential Improvements**

* The script is kept simple for clarity, but it can be enhanced in several ways:
  * **Stop Criteria:** Implementing a mechanism to terminate the optimization loop when convergence is reached.
  * **Advanced Design Procedures:** Incorporating more detailed methods to evaluate the load-bearing capacity of elements.
  * **Enhanced Analysis:** Including additional considerations such as buckling or multi-axis forces for more robust designs.

This structured approach to code ensures clarity and minimizes unintended interactions between components, making the process easier to follow and adapt for complex scenarios.

{% file src="/files/p4IoiYfmxKpP8HgRyHOz" %}

{% file src="/files/qoNaIMXj5n2oEHoqwmkS" %}


# 2.4.2: Activation and Deactivation of Elements

The example “**ActivationDeactivationOfElements.gh**” features a simplified version of Karamba3D’s **“Tension/Compression Eliminator”**-component. It repeatedly evaluates a structure and removes all elements with tensile normal force. Starting from an arbitrary structural model the script iteratively removes those elements which are under tension (see fig. 2.4.2.1).&#x20;

![Fig. 2.4.2.1: From the initial truss only those elements without tensile forces survive.](/files/-MXH_8u947z1mPElhI6N)

{% code lineNumbers="true" %}

````csharp

#region Usings
using System;

#r "C:\Program Files\Rhino 8\Plug-ins\Karamba\KarambaCommon.dll"
#r "C:\Program Files\Rhino 8\Plug-ins\Karamba\Karamba.gha"

using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

using Karamba.Models;
using Karamba.GHopper.Models;
using Karamba.Utilities;
using Karamba.Loads.Combination;
using Karamba.Elements.States.Selectors;
using Karamba.Geometry;

#endregion

public class Script_Instance : GH_ScriptInstance
{
    private void RunScript(
	object Model_in,
	int maxiter,
	ref object Model_out,
	ref object isActive,
	ref object maxDisp)
    {
        var k3d = new KarambaCommon.Toolkit();
        IReadOnlyList<double> max_disp;
        IReadOnlyList<Vector3> out_force;
        IReadOnlyList<double> out_energy;
        string warning;

        var model = Model_in as Model;
        if (model == null) {
            throw new ArgumentException("The input in 'Model_in' is not of type karamba.Models.Model!");
        }

        // load case to consider for elimination of elements
        string lcName = "LC0";
        // get load-case combination
        LoadCaseCombination lcc;
        model.lcActivation.TryGetLoadCaseCombination(lcName, out lcc);
        // select load case 0 of load-case combination "LC0"
        var stateSelector = new StateElement1DSelectorIndex(model, lcc, 0);

        // clone the model and its list of elements to avoid side effects
        model = model.Clone();
        // clone its elements to avoid side effects
        model.cloneElements();

        // do the iteration and remove elements with tensile axial forces
        for (int iter = 0; iter < maxiter; iter++) {

            // create a deform and response object for calculating and retrieving results
            model = k3d.Algorithms.Analyze(model, new List<string>(){lcName}, out max_disp, out out_force, out out_energy, out warning);

            // check the normal force of each element and deactivate those under tension
            double N, V, M;
            bool has_changed = false;
            foreach (Karamba.Elements.ModelElement elem in model.elems) {
                // retrieve resultant cross section forces
                elem.resultantCroSecForces(model, stateSelector, 0.3, 3, out N, out V, out M);

                // check whether normal force is tensile
                if (N > 0) {
                    // set element inactive
                    elem.set_is_active(model, false);
                    has_changed = true;
                }
            }

            // leave iteration loop if nothing changed
            if (!has_changed) break;

            // rebuild the C++ model
            model.buildFEModel();
        }

        // update model to its final state
        model = k3d.Algorithms.Analyze(model, new List<string>(){lcName}, out max_disp, out out_force, out out_energy, out warning);

        // set up list of true/false values that corresponds to the elemment states
        List<bool> elem_activity = new List<bool>();
        foreach (var elem in model.elems) {
            elem_activity.Add(elem.IsActive);
        }

        isActive = elem_activity;
        maxDisp = max_disp;
        Model_out = new GH_Model(model);

        Print("Everything OK");
    }
}
```
````

{% endcode %}

At the beginning of the script, the input variable `Model_in` is type-cast to `Karamba.Models.Model`. If the provided object does not match the expected type, an `ArgumentException` is thrown.

The load case name is fixed as `"LC0"` in line 51. The associated `LoadCaseCombination` object is retrieved from the model in line 54 and utilized in line 56 to create a result selector. This selector extracts the first (and only) load-case item of the load-case combination. In Karamba3D, a load case is equivalent to a load-case combination containing a single load-case item.

To prevent side effects, data must be copied before any modifications occur. Line 59 performs this operation for the Karamba3D model. Since the model contains references to objects, these objects must also be copied. The element activation state is modified within the script, so the list referencing these elements is copied as shown in line 61.

The model is analyzed repeatedly for the specified load-case combination `"LC0"` in line 67.

In lines 72–82, the algorithm iterates through all elements in the system, extracts their resultant section forces, and deactivates any members under tension (where *N* ≥*0*).

The loop terminates when no further changes are detected (line 85) or when the maximum number of iterations is reached. At this point, the model is updated one final time.

An iteration over all elements generates a list of boolean values corresponding to their activation states (lines 96–98). This list is assigned to the output variable in line 100. Finally, line 102 wraps the model containing the final results in a `GH_Model` wrapper object.

{% file src="/files/EoUUBgbQgPUk3M7Sw4P6" %}

{% file src="/files/PgoCKP9pKNHeltHPfl9K" %}


# 2.5: Data Export from Karamba3D

When it comes to exporting data from a Karamba3D model to another data-format one could simply go through the object tree of the model and iterate manually over the existing entities like e.g. nodes, elements, materials, . . . . The use of a builder pattern removes some of the bureaucratic overhead involved in this approach. The script in the example “**ModelExport.gh**” shows how to generate an XML-file based on a given Karamba3D-model. The output consists of a string which can be streamed to a file. When opened with a web-browser a nicely formatted tree results (see fig. 2.5.1).

![Fig. 2.5.1: ModelExport.gh](/files/-MXH_8mQ8Ka3_HUVaTMk)

By inheriting from **"Karamba.Exporters.ExportBuilder"** and overriding **"builder"**-methods a builder-class can be configured to export Karamba3D-models to any format - here XML.

The corresponding source-code looks like this:

```csharp
...
using System.Xml;
using System.IO;
using Karamba.Models;
using Karamba.Nodes;
using Karamba.CrossSections;
using Karamba.Elements;
using Karamba.Loads;
using Karamba.Materials;
using Karamba.Supports;
using Karamba.Geometry;
...
private void RunScript(object Model_in, ref object XML)
{
    var model = Model_in as Model;
    if (model == null) {
      throw new ArgumentException("The input is not of type model!");
    }

    var builder = new BuilderXML();
    var director = new Karamba.Exporters.ExportDirector();
    director.ConstructExport(model, builder);

    XML = builder.getProduct();
}

// <Custom additional code>
public class BuilderXML : Karamba.Exporters.ExportBuilder {
  // the XML document
  private XmlDocument doc_ = new XmlDocument();
  // the model inside the xml-document
  private XmlElement model_;

  public override void newProduct() {
    model_ = (XmlElement) doc_.AppendChild(doc_.CreateElement("K3DModel"));
  }

  public override void buildMaterial(FemMaterial m, int ind) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("FemMaterial"));
    xml_node.InnerText = "ind: " + ind + ":" + m.ToString();
  }

  public override void buildVertex(Node v) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("Node"));
    xml_node.InnerText = v.ToString();
  }

  public override void buildCroSec(CroSec crosec) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("CroSec"));
    xml_node.InnerText = crosec.ToString();
  }

  public override void buildElement(ModelElement e, Model model) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("ModelElement"));
    xml_node.InnerText = e.ToString();
  }

  public override void buildElementLoad(ModelElement elem, Model model) {
    foreach (var l in elem.Elem_loads) {
      var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("ElementLoad"));
      xml_node.InnerText = l.ToString();
    }
  }

  public override void buildLoadCase(int lc_ind, Model model) {
    Vector3 g_vec = new Vector3(0, 0, 0);
    foreach (GravityLoad g in model.gravities.Values)
    {
      if (model.lcActivation.FebLoadCaseInds(g.LcName).Contains(lc_ind))
      {
        g_vec = g.force;
      }
    }

    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("LoadCase"));
    xml_node.InnerText = "load-case: " + lc_ind + " g =" + g_vec;
  }

  public override void buildSupport(Support s) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("Support"));
    xml_node.InnerText = s.ToString();
  }

  public override void buildPointLoad(PointLoad p) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("PointLoad"));
    xml_node.InnerText = p.ToString();
  }

  public override void buildMeshLoad(MeshLoad m) {
    var xml_node = (XmlElement) model_.AppendChild(doc_.CreateElement("MeshLoad"));
    xml_node.InnerText = m.ToString();
  }

  public string getProduct() {
    StringWriter sw = new StringWriter();
    XmlTextWriter tx = new XmlTextWriter(sw);
    doc_.WriteTo(tx);
    string str = sw.ToString();//
    return str;
  }
}
// </Custom additional code>
```

The first part of the script comprises the “RunScript”-method. There the “Model\_in”-object gets converted to a Karamba3D-Model. In line 20 follows the creation of a builder-object which does the conversion-work. The class “BuilderXML” gets defined further below in the script. A director-object is instantiated in the next line. Its task consists of iterating over the model constituents and presenting them to the builder. Calling “ConstructExport” in line 22 initiates the creation process. Line 24 assigns the builder’s product to the output variable “XML”.

The “BuilderXML”-class derives from Karamba.Exporters.ExportBuilder and overrides some of its methods. In order to keep things simple each build-method adds a XML-node which contains the string-representation of the corresponding argument. In case of “ModelElement” the corresponding build-method does not know which type of element gets passed. Thus one has to apply pattern matching to find get the right type (e.g. “ModelBeam”, “ModelTruss”, “ModelShell” or “ModelSpring”)

{% file src="/files/vVjS7psEKU9l6EGkFojV" %}


# 2.6: The VB Script Component

The steps for setting up a VB script component for using Karamba3D are analogous to those described in section [2.1](/2.-scripting-with-karamba3d-inside-grasshopper/2.1-hello-karamba3d). In order to run the above example in VB you could use one of the many C# to VB converters (see e.g. [http://www.developerfusion.com/tools/convert/csharp-to-vb/](https://www.developerfusion.com/tools/convert/csharp-to-vb/)) to get the corresponding VB source text.


# 2.7: The IronPython Component

IronPython is the .NET implementation of Python, providing compatibility with the .NET framework. Python, as an open and platform-independent scripting language, includes a vast array of libraries. However, not all Python libraries are compatible with IronPython due to its .NET-based constraints.

* **Grasshopper in Rhino 5**: To use Python scripting in Grasshopper under Rhino 5, you need to install **GHPython**, which can be downloaded from Food4Rhino.
* **Grasshopper in Rhino 6 and 7**: GHPython is included by default in Grasshopper for Rhino 6 and 7, requiring no additional installation.

{% content-ref url="/pages/-MXH\_8OPHx5jsPoTq9B\_" %}
[2.7.1: Results Retrieval on Shells](/2.-scripting-with-karamba3d-inside-grasshopper/2.7-the-ironpython-component/2.7.1-results-retrieval-on-shells)
{% endcontent-ref %}

{% content-ref url="/pages/-MXH\_8OQR8ZuIhMNpa3m" %}
[2.7.2: A Simplified ESO-Procedure on Shells](/2.-scripting-with-karamba3d-inside-grasshopper/2.7-the-ironpython-component/2.7.2-a-simplified-eso-procedure-on-shells)
{% endcontent-ref %}


# 2.7.1: Results Retrieval on Shells

The example file “**SimpleShellESO.gh**” (see fig. 2.7.1.1) contains two Python-scripts which will be explained below. The first script retrieves results from Karamba3D’s triangular shell elements:

![Fig. 2.7.1.1: "SimpleShellESO"](/files/-MXH_8WYq_ub2wIxEkXc)

Two python scripts: the first retrieves shell results (see code block below), the second one performs a simplified variant of an evolutionary structural optimization (ESO) procedure (see section [2.7.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.7-the-ironpython-component/2.7.2-a-simplified-eso-procedure-on-shells)).

{% code lineNumbers="true" %}

```python
import clr

clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 8\Plug-ins\karamba\karamba.gha")
clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 8\Plug-ins\karamba\karambaCommon.dll")

import Karamba.Models.Model as Model
import Karamba.Elements.ModelShell as Shell
import feb.ShellMesh as ShellMesh
import feb.TriShell3D as TriShell
import feb.VectSurface3DSigEps as TriStates
import feb.EnergyVisitor as EnergyVisitor

for element in Model_in.elems:
   if type(element) != Shell:
       continue
       
   print "shell!"
   femesh = Model_in.febmodel.triMesh(element.fe_id)
   n_tri = femesh.numberOfElems()
   print "number of triangle elements:", n_tri
   
   energy_visitor = EnergyVisitor(Model_in.febmodel, Model_in.febmodel.state(0), 0);
   energy_visitor.visit(Model_in.febmodel);
            
   for ind in xrange(n_tri):
       print "Axial Energy ", energy_visitor.axialEnergy(Model_in.elems[0].fe_id + ind)
       print "Bending Energy ", energy_visitor.bendingEnergy(Model_in.elems[0].fe_id + ind)
   
   zRel = 0.0
   febLCInd = 0
   factor = 1.0
   layerInd = 0
   tri_states = femesh.elementSigEps(Model_in.febmodel, zRel, febLCInd, factor, layerInd)
   for tri_state in tri_states:
       s = tri_state.sig_princ()
       print "first principal stress (kN/cm2):",s.x()/10000
       print "second principal stress (kN/cm2):",s.y()/10000
       
print "Number of elements", Model_in.elems.Count
```

{% endcode %}

Lines 3 and 4 reference the Karamba3D DotNet-assemblies. Depending on how you installed Rhino it might be necessary to adapt the paths. In case that one of them can not be located an error will be issued.

Plug the output of **“out”** into a panel. If there is only one line of output, type **“GrasshopperDeveloperSettings”** in the Rhino text window and check whether the **“Memory load \*.GHA assemblies using COFF byte arrays”**-option is unhooked.

The retrieval of axial- and bending-energies works via the visitor-pattern (see \[[2](/bibliography)] for details on that). Line 22 creates such a visitor object for the elastic element energies by handing over a reference to a C++ model, a state, a load-case index and possibly a load-case factor. Unless one performs non-linear calculations state **“0”** is the right one to chose.

A shell patch consists of several shell elements. This might lead to some confusion since in the Grasshopper UI shell patches are named **“elements”**, in the C++ model however the patches consist of several triangular shell elements. In lines 26 and 27 the property **“fe\_ind”** returns the index of a C++ element that corresponds to a given C#-element. In case of shell-patches this corresponds to the first C++-element.

Other shell results like principal stresses can be retrieved directly from the FE-mesh like in lines 29 to 37.

{% file src="/files/lqzkGIm7PldDGBiEIsxh" %}

{% file src="/files/LiEO6v4C5ZU9OQln0e69" %}


# 2.7.2: A Simplified ESO-Procedure on Shells

The example file “**SimpleShellEso.gh**” contains also a Python script which performs a simple evolutionary structural optimization (ESO) procedure on shell elements. As it applies no filters for calculating the fitness of individual shell triangles checkerboard patterns result (see fig. 2.7.2.1). Alas the script can be easily extended to include more elaborate fitness calculation schemes.

![Fig. 2.7.2.1: SimpleShellEso.gh](/files/-MXH_8WYq_ub2wIxEkXc)

The script shows how to work directly with the C++ model in order to avoid costly mappings to and from the C#-model:

{% code lineNumbers="true" %}

```python
import clr

clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 8\Plug-ins\karamba\Karamba.gha")
clr.AddReferenceToFileAndPath("C:\Program Files\Rhino 8\Plug-ins\karamba\KarambaCommon.dll")

import Karamba.Models.Model as Model
import Karamba.Elements.ModelShell as Shell
import Karamba.Materials.FemMaterial_Isotrop as FemMaterial
import Karamba.Utilities.Utils as Utils
import feb.ShellMesh as ShellMesh
import feb.TriShell3D as TriShell3D
import feb.VectSurface3DSigEps as TriStates
import feb.Deform as Deform
import feb.Response as Response
import feb.EnergyVisitor as EnergyVisitor
import Rhino.Geometry as Rh

from operator import attrgetter

# encapsulate ESO properties of shell elements
class EsoItem:
    def __init__(self, shell_elem, elem_ind):
       self.active = True
       self.fitness = 0
       self.shell_elem = shell_elem
       self.area = shell_elem.area()
       self.ind = elem_ind

    def update(self, energy_visitor):
       self.fitness = energy_visitor.elasticEnergy(self.ind) / self.area
        

# clone model to avoid side effects
model = Model_in.Clone()
model.deepCloneFEModel()

# generate ESO properties of each triangular shell element
eso_items = []
for elem in model.elems:
    if type(elem) != Shell:
       continue
    tri_mesh = model.febmodel.triMesh(elem.fe_id)
    for i in xrange(tri_mesh.numberOfElems()):
        eso_items.append(EsoItem(tri_mesh.elem(i), i))

nremove_per_iter = int(NRemove/NIter+1)
n_removed = 0

# do the ESO iterations
for iter in xrange(NIter):
    analysis = Deform(model.febmodel)
    response = Response(analysis)

    loadCaseCombinationIndex = 0
    Utils.handleError(response.update(loadCaseCombinationIndex), analysis);

    energy_visitor = EnergyVisitor(model.febmodel, model.febmodel.state(0), 0);
    energy_visitor.visit(model.febmodel);
   
    for eso_item in eso_items:
        eso_item.update(energy_visitor)

    eso_items = sorted(eso_items, key = attrgetter("fitness"))

    n_removed_per_iter = 0
    has_changed = False
    for eso_item in eso_items:
        if (n_removed >= NRemove): break
        if (n_removed_per_iter >= nremove_per_iter): break
        if (eso_item.active == False):
            continue
        eso_item.shell_elem.softKilled(True)
        eso_item.active = False
        n_removed +=1
        n_removed_per_iter +=1
     
    has_changed = True
    if (has_changed == False):
        break
    model.febmodel.touch()

    # create active and inactive mesh for output
    active_mesh = Rh.Mesh()
    inactive_mesh = Rh.Mesh()
    for i in xrange(model.febmodel.numberOfNodes()):
        feb_pos = model.febmodel.node(i).pos()
        active_mesh.Vertices.Add(Rh.Point3d(feb_pos.x(), feb_pos.y(), feb_pos.z()))
        inactive_mesh.Vertices.Add(Rh.Point3d(feb_pos.x(), feb_pos.y(), feb_pos.z()))
        
    for eso_item in eso_items:
        ind0 = eso_item.shell_elem.node(0).ind()
        ind1 = eso_item.shell_elem.node(1).ind()
        ind2 = eso_item.shell_elem.node(2).ind()
        if (eso_item.active):
            active_mesh.Faces.AddFace(Rh.MeshFace(ind0, ind1, ind2))
        else:
            inactive_mesh.Faces.AddFace(Rh.MeshFace(ind0, ind1, ind2))
                
    activeMesh = active_mesh
    inactiveMesh = inactive_mesh
```

{% endcode %}

In the above code the class **“ESOItem”** handles the book-keeping necessary in the optimization steps. It contains the activation-state, the fitness, the element’s area, a reference to the C++-element and the element’s index in the C#-model. The **“update”**-method calculates the specific elastic energy of the underlying shell-element.

Activation and deactivation of model elements works via setting the soft-kill status of C++-elements to **“True”** or **“False”** (see line 74). On model-assembly the stiffness of the corresponding element will be multiplied with the soft-kill-factor which is **1.0×10^−10**. This factor can be set on the C++-model via **“softKillFactor(new\_factor)”** if necessary.

The last part of the script categorizes the shell-faces into active or in-active adding their geometry to the corresponding output-meshes.

{% file src="/files/22Egiv2MXL1uhoZvaY0w" %}

{% file src="/files/BNBNND5CcCVV1sZj5SDj" %}


# 2.8: The Python 3 Component

With Rhino 8 it is now possible to use the full capabilities of Python without the limitations that came with IronPython regarding the availability of libraries.

In the following the C# examples of sections 2.4 to 2.5 will be discussed using Pthon3.


# 2.8.1: Hello Karamba3D

In order to get started with Python3 scripting in Grasshopper place a "Python 3 Script"-component on the canvas. Details regarding its functionality can be found [here](https://developer.rhino3d.com/guides/scripting/scripting-gh-python/).

<figure><img src="/files/GMlVt530CiudKDRB5XuB" alt=""><figcaption><p>Fig. 2.8.1.1: A minimal K3D-model for retrieving the number of elements, materials and cross sections via Python 3.</p></figcaption></figure>

When Karamba3D is installed in your Grasshopper the following code retrieves properties of a Karamba3D model:

```
import Karamba

model = Model_in;

if not isinstance(model, Karamba.Models.Model):
    raise Exception("The input is not of type 'Model'")

print(f"Number of Elements: {model.elems.Count}")
print(f"Number of Materials: {model.materials.Count}")
print(f"Number of Cross sections: {model.crosecs.Count}")
```

The source code is explained in [section 2.1](/2.-scripting-with-karamba3d-inside-grasshopper/2.1-hello-karamba3d).

{% file src="/files/0srqSDT3IslBbFOZjEfw" %}


# 2.8.2: Data Retrieval from Models

The following constitutes the Python 3 version of the example discussed in section 2.8.2. This is the source code of the Python component:

```
import Karamba

model = Model_in;

if not isinstance(model, Karamba.Models.Model):
    raise Exception("The input is not of type 'Model'")

matWeights = {}

for elem in model.elems:
    mat = elem.crosec.material
    if not(mat in matWeights):
        matWeights[mat] = 0.0

    matWeights[mat] += elem.weight(model.nodes)

ucf = Karamba.Utilities.UnitsConversionFactory.Conv();
mass = ucf.force2mass();
kg = ucf.kg();

for key, value in matWeights.items():
    print(f"Material: {key.name}: {kg.toUnit(mass.toBase(value))}" + kg.unitB)
```

{% file src="/files/pXKMwQmxHelnm1OF3hKU" %}


# 2.8.3: How to Create Structural Models

The detailed account of the equivalent C# example can be found in [section 2.3](/2.-scripting-with-karamba3d-inside-grasshopper/2.3-how-to-create-structural-models). Here the Python 3 sourcecode and the Grasshopper definition:

```
from Karamba.Utilities import MessageLogger
from Karamba.Geometry import *
import Karamba.Loads
from System.Collections.Generic import List
from System import String

import KarambaCommon

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

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

bending = True
limitDist = 0.005
elems, nodes = k3d.Part.LineToBeam(L0, "B1", None, logger)

cond = [True, True, True, True, True, True]
support = k3d.Support.Support(0, cond)
supports = [ support ]

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

# for Karamba3D Version <= 3.1.41119
model, info, mass, cog, info, runtimeWarning = k3d.Model.AssembleModel(
    elems, 
    supports, 
    List[Karamba.Loads.Load](ploads))

# for Karamba3D Version > 3.1.41119
# model, info, mass, cog, info, runtimeWarning = k3d.Model.AssembleModel(
#     elems, 
#     supports, 
#     ploads)    

# for Karamba3D Version <= 3.1.41119
loadCases = List[String](["LC0"])

# for Karamba3D Version > 3.1.41119
# loadCases = ["LC0"]

model_analysed, maxDisp, outForce, outEnergy, Warning = k3d.Algorithms.Analyze(model, loadCases)

ucf = Karamba.Utilities.UnitsConversionFactory.Conv();
cm = ucf.cm();
print(f"max disp: {cm.toUnit(maxDisp[0])}" + cm.unitB);

Model_out = Karamba.GHopper.Models.GH_Model(model_analysed);
```

{% file src="/files/dLFf1zPVJOJ3H7QpgqH6" %}


# 2.8.4: How to Modify Structural Models

Here the Python 3 version of the examples explained in [section 2.4](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models).


# 2.8.4.1: Cross section Optimization

The equivalent Python 3 code of the example in [section 2.4.1](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.1-cross-section-optimization) looks like this:

{% code lineNumbers="true" %}

```
import Karamba.Models
import Karamba.CrossSections
import Karamba.Elements
import Karamba.Results
import KarambaCommon
from Karamba.Geometry import *
from System.Collections.Generic import List
from System import String

model = Model_in
if not isinstance(model, Karamba.Models.Model):
      raise Exception("The input in 'Model_in' is not of type Karamba.Models.Model!")

for crosec in CroSecs_in:
    if not isinstance(crosec, Karamba.CrossSections.CroSec_Beam):
        raise Exception("The input in 'CroSecs_in' contains objects which are not of type Karamba.CrossSections.CroSec_Beam!")

# avoid side effects
model = model.Clone();
model.cloneElements();

k3d = KarambaCommon.Toolkit()
for i in range(niter):
    loadCases = List[String]([lcName])
    model, max_disp, outForce, outEnergy, Warning = k3d.Algorithms.Analyze(model, loadCases);

    for elem_ind in range(model.elems.Count):
        beam = model.elems[elem_ind]
        if not isinstance(beam, Karamba.Elements.ModelBeam):
            continue

        # avoid side effects
        beam = beam.Clone();
        model.elems[elem_ind] = beam;

        elemId = List[String](str(elem_ind))
        N, V, M = Karamba.Results.BeamResultantForces.solve(model, elemId, lcName, 100, 1);

        for crosec in CroSecs_in:
            beam.crosec = crosec
            maxSigma = abs(N[0][0]) / crosec.A + M[0][0] / crosec.Wely_z_pos;
            ft = crosec.material.ft()
            if (maxSigma < ft):
                break
          
    model.initMaterialCroSecLists();
    model.buildFEModel();

model, max_disp, outForce, outEnergy, Warning = k3d.Algorithms.Analyze(model, loadCases);
Disp_out = max_disp[0];
Model_out = Karamba.GHopper.Models.GH_Model(model);
```

{% endcode %}

{% file src="/files/KkprLgUWjOPTXkPEfP17" %}


# 2.8.4.2: Activation and Deactivation of Elements

The equivalent Python 3 code of the example in [section 2.4.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements) looks like this:

{% code lineNumbers="true" %}

```
import Karamba.Models
import Karamba.CrossSections
import Karamba.Elements
import Karamba.Results
import KarambaCommon
from Karamba.Elements.States.Selectors import StateElement1DSelectorIndex
from Karamba.Geometry import *
from System.Collections.Generic import List
from System import String

model = Model_in
if not isinstance(model, Karamba.Models.Model):
      raise Exception("The input in 'Model_in' is not of type Karamba.Models.Model!")

# load case to consider for elimination of elements
lcName = "LC0";
lcNames = List[String]([lcName])
# get load-case combination
success, lcc = model.lcActivation.TryGetLoadCaseCombination(lcName)
# select load case 0 of load-case combination "LC0"
stateSelector = StateElement1DSelectorIndex(model, lcc, 0);

# clone the model and its list of elements to avoid side effects
model = model.Clone();
# clone its elements to avoid side effects
model.cloneElements();

k3d = KarambaCommon.Toolkit()

# do the iteration and remove elements with tensile axial forces
for iter in range(maxiter):
    # create a deform and response object for calculating and retrieving results
    model, max_disp, outForce, outEnergy, Warning = k3d.Algorithms.Analyze(model, lcNames)

    # check the normal force of each element and deactivate those under tension
    has_changed = False;
    for elem in model.elems:
        # retrieve resultant cross section forces
        N, V, M = elem.resultantCroSecForces(model, stateSelector, 0.3, 3)

        # check whether normal force is tensile
        if (N > 0):
            # set element inactive
            elem.set_is_active(model, False)
            has_changed = True

    # leave iteration loop if nothing changed
    if not has_changed:
        break

    # rebuild the C++ model
    model.buildFEModel()

# update model to its final state
model, max_disp, outForce, outEnergy, Warning = k3d.Algorithms.Analyze(model, lcNames)

# set up list of true/false values that corresponds to the elemment states
elem_activity = []
for elem in model.elems:
    elem_activity.append(elem.IsActive);


isActive = elem_activity;
maxDisp = max_disp;
Model_out = Karamba.GHopper.Models.GH_Model(model)

print("Everything OK");
```

{% endcode %}

{% file src="/files/qfg6gIUmyrhEmhuXfNg2" %}


# 3.1: Setting up a Visual Studio Project for GH Plug-ins

In case of larger scripting projects advanced debugging facilities and the organization of source code in neatly separated files makes life easier. Integrated development environments like Microsoft Visual Studio offer these possibilities – and some more. The **“Community”**-version of Visual Studio can be downloaded for free from the Microsoft web-site.

A useful GH related tool for Visual Studio can be found at: <https://marketplace.visualstudio.com/items?itemName=McNeel.GrasshopperAssemblyforv6>

It contains project and component wizards which take care of the project settings and boiler-plate code necessary to create valid GH-components.


# 3.2: Basic Component Setup

The example in this section assumes that you have Visual Studio 2017 and the above mentioned GH add-on installed. In order to make things easy, the script will be analogous to that presented in section [2.4.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements). Follow these steps to set up a project for creating a GH component which makes use of Karamba3D functionality:

1. Start Visual Studio and select **“File/New/Project . . . ”** from the main menu. A Window appears which lets you select from among different project types (see fig 3.2.1).
2. Go to the bottom of the window and set the name of the project to **“TensionElim”**.

   **Grasshopper loads plug-ins according to the alphanumeric order of their file-names. If a component intends to use Karamba3D functionality make sure its name comes after “Karamba3D” otherwise it will not be able to reference it.**
3. Browse to a folder where your project shall be created using the **“Browse”**-button
4. Unhook the **“Create directory for solution”**-option.
5. Select “Visual C#” from the right hand side tree-menu of installed templates. In case you want to script in VB select the corresponding entry. Double-click on **“Grasshopper Add-On”** in the middle part of the window (see fig. 3.2.1).
6. Another window appears that lets you configure the properties of the Grasshopper component.
7. You can now change the name, nick-name, category (e.g. **“Karamba3D”**) and subcategory (e.g. **“Extra”**) under which the component appears inside Grasshopper – but they can also be changed later on.
8. In case that the path to Grasshopper, RhinoCommon or Rhino appears in red click on the “...” button and point to the right directory.
9. Press **“Finish”**. The GH component wizard now creates the project **“TensionElim”** which already contains the basic frame for a GH component.

![Fig. 3.2.1: Select Grasshopper Add-On from the templates available for C# or VB.](/files/-MXH_8fhMgDALglovQs3)

Select **“View/SolutionExplorer”** to get a list of all files that belong to the project. The file **“Assem- blyInfo.cs”** in the **“Properties”**-folder of the SolutionExplorer lets you set the title of your assembly, a description, a copyright message and so on. The folder **“References”** lists all those assemblies which the component borrows functionality from. By default these are **“GH\_IO”**, **“Grasshopper”**, **“RhinoCommon”** and some system assemblies. In **“TensionElimComponent.cs”** you will find the definition of the class **“TensionElimComponent”**. It inherits its functionality from the class **“GH\_Component”** and thus lets you define the properties and behavior of the component later visible in GH.

As a first try right-click on the project **“TensionElim”** and select **“Build”** from the context menu. If all goes well the file **“TensionElim.gha”** gets created in the **“bin”**-folder of the Visual Studio project. Copy this file to one of the places where Grasshopper looks for plug-ins at start-up. Among others these two choices exist:

* The **“Libraries”**-folder of Grasshopper. It sits in the user-directors under **“C:/Users/Username/App- Data/Roaming/Grasshopper/Libraries”** an can be accessed from within Grasshopper via the menu **“File/Special Folders/Components Folder”**. The advantage of putting a plug-in there is, that a user does not need any special privileges to install it.&#x20;
* The **“Plug-ins”**-folder of Rhino to be found at e.g. **“C:/Program Files/Rhino7”** when working with Rhinoceros 7. Placing a plug-in there makes it accessible to all users of the computer. The disadvantage of this file location lies in the fact that one needs to have admin-rights to install a file there.

Karamba3D lives in the **“Plug-ins”**-folder of Rhino. So plug-ins which reference its functionality need to be placed there as well.

In order to avoid the manual copying of the **“.gha”**-file you can setup a post-build-event in the Visual Studio Project settings – this makes debugging more comfortable: Right-click on the project entry in the Solution Explorer, select **“Properties”**, choose **“Build Events”** from the left-hand tabs, and add in one line at the end of the **“Post-build event command line”** text window:

`Copy ’’$(TargetDir)$(ProjectName).gha’’`  \
`’’C:\Users\YourUserNameHere!!\App Data\Roaming\Grasshopper\Libraries\$(ProjectName).gha’’`

or

`Copy ’’$(TargetDir)$(ProjectName).gha’’`  \
`’’C:\ProgramFiles\Rhino6\$(ProjectName).gha’’`

In case you use Rhino 6. Do not forget to replace **“YourUserNameHere!!”** by your user name in case of option one. For option one has to assume ownership of of the Rhino **“Plug-ins”**-folder and acquire write-rights otherwise the copy command fails.

Upon starting (or restarting) GH there should now show up a new icon in the category and subcategory previously provided by you in the constructor of the **“TensionElimComponent”**-class (see fig. 3.2.2). As yet there is no image attached to the new button. Therefore it shows up as a circle filled with black and white rectangles. Take a look at the function **“Icon”** of the **“TensionElimComponent”**- class to change this if you want. You will also notice that the new component has neither input- nor output-plugs.

![Fig. 3.2.2: First step: Custom component without input- or output-plugs.](/files/-MXH_8fjskXRg0xjJw7l)


# 3.3: How to Reference Karamba3D Assemblies

In order to package the script of section [2.4.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements) into a GH-component there needs to be one input-plug that receives a Karamba3D model. Three output plugs return an updated Karamba3D model, a list of boolean values that signifies which elements are active or not and the value of the largest resultant displacement.

The following example can be found in the examples that accompany this guide. Go to **“#/TensionElim”** and double-click on **“TensionElim.sln”** to open the project with Visual Studio. Depending on what version of Grasshopper you use it will be necessary to reestablish the project references.

**Referencing Required Libraries**: To use Karamba3D classes (e.g., the `Model` class), you must reference the `karambaCommon.dll` library. There are two ways to do this:

* **Using the KarambaCommon NuGet Package**:
  * In Visual Studio, go to **Tools > NuGet Package Manager > Manage NuGet Packages for Solution...** and install the `KarambaCommon` package.
* **Referencing the Installed Library**:
  * Right-click **References** in the Solution Explorer and select **Add Reference...**.
  * In the tabbed view, go to **Browse** and locate the `karambaCommon.dll` file in Rhino’s **Plug-ins** folder.

**Including `karamba.gha`**: Since Karamba3D will be used in Grasshopper, you must also reference `karamba.gha`. However, Visual Studio does not natively allow `.gha` files to be selected. Use the following workaround:

* Save and close your Visual Studio project.
* In the project directory, locate the `TensionElim.csproj` file and open it in a text editor.
* Search for the `karambaCommon` entry, copy it, and modify it to reference `karamba.gha` (see Fig. 3.3.1 in the guide).
* Save the file and reopen the project in Visual Studio.

![Fig. 3.3.1: Work-around for referencing "karamba.gha" in Visual Studio: Edit the "TensionElim.csproj"-file](/files/-MXH_8rRzU7AbedXEH2W)

**Adjusting Reference Properties**:

* After adding `karamba` and `karambaCommon` references, right-click each reference in **Solution Explorer** and select **Properties**.
* Set the **Copy Local** property (default: `True`) to `False`.

Following these steps ensures the script is properly packaged into a custom Grasshopper component for use with Karamba3D.


# 3.4: Input- and Output-Plugs

Now it is time to add the input- and output-plugs to the new component. This is the listing of the first few lines of **“TensionElimComponent.cs”** which implements this functionality:

{% code lineNumbers="true" %}

```csharp
using System;
using System.Collections.Generic;

using Grasshopper.Kernel;

using Karamba.Models;
using Karamba.GHopper.Models;
using Karamba.Loads.Combinations;

namespace TensionElim {
    public class TensionElimComponent : GH_Component
    {
        public TensionElimComponent()
            : base("TensionElim", "TenElim",
                ".", "Karamba" , "Extra" )
                {
                }

                protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
                {
                    pManager.AddParameter(new Param_Model(), "Model_in", "Model_in",
                        "Model to be manipulated", GH_ParamAccess.item);
                }

                protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
                {
                    pManager.RegisterParam(new Param_Model(), "Model_out", "Model_out",
                        "Model after eliminating all tension elements");
                    pManager.Register_BooleanParam("isActive", "isActive",
                        "List of boolean values corresponding to each element in the model." +
                        "True if the element is active.");
                    pManager.Register_NumberParam("maximum displacement", "maxDisp",
                        " Maximum displacement [m] of the model after eliminationprocess.");
                }
        ...
    }
```

{% endcode %}

Line 1 to 4 get automatically created by the GH-wizard. Lines 6 to 8 are important: they make the classes available (i.e. Model, Param\_Model, GH\_Model, Element, ...) which reside in the namespaces **“Karamba.Models”** and **“Karamba.GHopper.Models”**. This allows to define the component input in lines 21 and 22. Objects that are used as component input or output need to be wrapped so that GH can handle them. In Karamba3D these wrapper classes are named after the class they wrap preceded by **“Param\_”** and **“GH\_”**. Lines 27 to 33 specify the output plugs. Supplement **“TensionElim- Component.cs”** with the above lines, compile the project and copy the resulting **“TensionElim.gha”** as before. Restart Rhino.\
Fig. 3.4.1 shows the component with input and output-plugs.

![Fig. 3.4.1: Second step: Custom component with input- and output-plugs but no functionality yet.](/files/-MXH_8ocRinDF5bDk5dM)


# 3.5: Adding Functionality to a GH Component

In order to make the component do something one has to add code to the **“SolveInstance”** function in **“TensionElimComponents”**:

```csharp
protected override void SolveInstance(IGH_DataAccess DA)
{
    GH_Model in_gh_model = null;
    if (!DA.GetData<GH_Model>(0, ref in_gh_model)) return;
    var model = in_gh_model.Value;

    // maximum number of iterations
    int max_iter = 10;
    DA.GetData<int>(1, ref max_iter);

    // load case to consider for elimination of elements
    int lc_num = 0;

    // clone model to avoid side effects
    model = (Karamba.Models.Model)model.Clone();

    // clone its elements to avoid side effects
    model.cloneElements();

    // clone the feb-model to avoid side effects
    model.deepCloneFEModel();

    string singular_system_msg = "The stiffness matrix of the system is singular.";

    // do the iteration and remove elements with tensile axial forces
    for (int iter = 0; iter < max_iter; iter++)
    {
        // create an analysis and response object for calculating and retrieving results
        feb.Deform analysis = new feb.Deform(model.febmodel);
        feb.Response response = new feb.Response(analysis);

        try
        {
            // calculate the displacements
            response.updateNodalDisplacements();
            // calculate the member forces
            response.updateMemberForces();
        }
        catch
        {
            // send an error message in case something went wrong
            throw new Exception(singular_system_msg);
        }

        // check the normal force of each element and deactivate those under tension
        double N, V, M;
        bool has_changed = false;
        foreach (var elem in model.elems)
        {
            // retrieve resultant cross section forces
            elem.resultantCroSecForces(model,  new LCSuperPosition(lc_num, model), 
                out N, out V, out M);
            // check whether normal force is tensile
            if (!(N >= 0)) continue;
            // set element inactive
            elem.set_is_active(model, false);
            has_changed = true;
        }

        // leave iteration loop if nothing changed
        if (!has_changed) break;

        // if something changed inform the feb-model about it (otherwise it won't recalculate)
        model.febmodel.touch();

        // this guards the objects from being freed prematurely
        GC.KeepAlive(analysis);
        GC.KeepAlive(response);
    }

    // update model to its final state
    double max_disp = 0;
    try
    {
        // create an analysis and response object for calculating and retrieving results
        feb.Deform analysis = new feb.Deform(model.febmodel);
        feb.Response response = new feb.Response(analysis);

        // calculate the displacements
        response.updateNodalDisplacements();
        // calculate the member forces
        response.updateMemberForces();

        max_disp = response.maxDisplacement();

        // this guards the objects from being freed prematurely
        GC.KeepAlive(analysis);
        GC.KeepAlive(response);
    }
    catch
    {
        // send an error message in case something went wrong
        throw new Exception(singular_system_msg);
    }

    // set up list of true/false values that corresponds to the element states
    List<bool> elem_activity = new List<bool>();
    foreach (var elem in model.elems)
    {
        elem_activity.Add(elem.IsActive);
    }

    DA.SetData(0, new GH_Model(model));
    DA.SetDataList(1, elem_activity);
    DA.SetData(2, max_disp);
}
```

In line 3 a wrapper-object for a Karamba3D model gets initialized to null and set to the value of the input plug of index **“0”**. The **“if”** statement in line 4 checks whether there is any data to process. In line 5 the Karamba3D-model gets retrieved from the GH-wrapper. In lines 8 to 9 the value of the **“maxiter”** plug-in gets read.

All the rest down to line 101 constitutes more or less a replica of the code of section [2.4.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements). Lines 102 to 104 transfer the results of the algorithm to the output-plugs.

The example **“ActivationDeactivationOfElements\_CustomComponent”** (see fig. 3.5.1) shows that the results using the GH custom component are the same as in section [2.4.2](/2.-scripting-with-karamba3d-inside-grasshopper/2.4-how-to-modify-structural-models/2.4.2-activation-and-deactivation-of-elements).

A comparison with the listing in section [2](/2.-scripting-with-karamba3d-inside-grasshopper/2.1-hello-karamba3d) shows that only the first and last parts differ significantly:

![Fig. 3.5.1: Elimination of elements under tension. this time using a custom GH-component.](/files/-MXH_8myEJNQ7wI6gcCZ)

Visual Studio Project File:

{% file src="/files/ARJmG3ILclLnyibtwoPj" %}


# Bibliography

|      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \[1] | Joseph Albahari. C# 7.0 in a Nutshell. O’Reilly UK Ltd., 2017. ISBN 1.491987650. URL [https://www.ebook.de/de/product/29084295/joseph\_albahari\_c\_7\_0\_in\_a\_ nutshell.html](https://www.oreilly.com/library/view/c-70-in/9781491987643/).                                                                                                                                                                                                                                                                                                                                                                                         |
| \[2] | Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Pat- terns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional, 199.4. ISBN 8601.4190.477.41. URL [https://www.amazon.com/ Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612? SubscriptionId=AKIAIOBINVZYXZQZ2U3A\&tag=chimbori05-20\&linkCode=xm2\&camp=2025\&creative=165953\&creativeASIN=0201633612](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612?SubscriptionId=AKIAIOBINVZYXZQZ2U3A\&tag=chimbori05-20\&linkCode=xm2\&camp=2025\&creative=165953\&creativeASIN=0201633612). |
| \[3] | Mark Michaelis. Essential C# 7.0. Microsoft Press, 2018. ISBN 1509303588. URL <https://www.ebook.de/de/product/28341699/mark_michaelis_essential_c_7_0.html>.                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| \[4] | Jon Skeet. C# in Depth. Manning, 2019. ISBN 161729.4535. URL [https://www.ebook.de/de/ product/30504497/jon\_skeet\_c\_in\_depth.html](https://www.ebook.de/de/product/30504497/jon_skeet_c_in_depth.html).                                                                                                                                                                                                                                                                                                                                                                                                                            |


