Overview

With the release of ETABS v22.0.0 , SAFE v22.0.0 , SAP2000 v26.0.0 , and CSiBridge v26.0.0 , it is now possible to create plugins for CSI products using .NET 8 . To learn more about the .NET 8 platform , please see What's new in .NET 8 . This example will guide a developer through creating a simple .NET 8 plugin using the C# programming language. By making use of the cross-product CSI API library, this example is compatible with ETABS v22.0.0 , SAFE v22.0.0 , SAP2000 v26.0.0 , and CSiBridge v26.0.0 (or a higher version of any of these). For an example plugin using .NET Framework, please see NET Framework Plugin Example .

Walkthrough

Prerequisites

This walkthrough uses Microsoft Visual Studio 2022 and the .NET 8 SDK. One of the following CSI products must be installed on the development computer: ETABS v22.0.0 , SAFE v22.0.0 , SAP2000 v26.0.0 , or CSiBridge v26.0.0 (higher versions of any product will also work)

Procedure

  1. Selecting C# as the language, create a new project of type Class Library. Ensure that the selected project type targets .NET or .NET Standard, not .NET Framework.


2. For convenience, set your project name to the desired name of your plugin. For this example, we will use the name CSiNET8PluginExample1


3. Select .NET 8.0 as the desired Framework


4. Once your project is created, add a reference to the CSI API library. This will be located in the installed program directory, e.g. C:\Program Files\Computers and Structures\ETABS 22\ .  


To create a plugin compatible with multiple CSI products, use the cross-product API library, CSiAPIv1.DLL . This library contains interfaces for all methods supported by all CSi products. Please note, however, that no CSI product implements every interface. Attempting to call an unsupported API method will return an error code. Developers should refer to the API documentation in the installed program directory to determine which methods are supported.

If you are making a plugin only for one CSI product, you can use the program-specific API library instead of the cross-product library. The program-specific API libraries are:

ProgramAPI Library
ETABSETABSv1.DLL
SAFESAFEv1.DLL
SAP2000SAP2000v1.DLL
CSiBridgeCSiBridge1.DLL


5. Almost all plugins will depend on other, non-CSI libraries to execute complicated functionality. To demonstrate the correct management of dependent libraries, this plugin makes trivial use of the popular Newtonsoft.Json serialization/deserialization library. It is freely available on GitHub and can be added to your project using the NuGet package manager within Visual Studio.


5. Some manual edits must be made to the project file in order for the plugin to run successfully. Open the project file (in our example, CSiNET8PluginExample1.csproj ) in a text editor, and make the following edits:


After the above changes, your project file should look approximately as shown below on the right. It's ok if doesn't match exactly.

Before:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>	
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>AnyCPU;x64</Platforms>
	</PropertyGroup>

	<ItemGroup>
	  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
	</ItemGroup>

	<ItemGroup>
	  <Reference Include="CSiAPIv1">
	    <HintPath>..\Program Files\Computers and Structures\ETABS 22\CSiAPIv1.dll</HintPath>
		<Private>false</Private>
		<ExcludeAssets>runtime</ExcludeAssets>
	  </Reference>
	</ItemGroup>

</Project>


After:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0-windows</TargetFramework>	
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>AnyCPU;x64</Platforms>
	 	<UseWindowsForms>true</UseWindowsForms>
		<EnableDynamicLoading>true</EnableDynamicLoading>
	</PropertyGroup>

	<ItemGroup>
	  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
	</ItemGroup>

	<ItemGroup>
	  <Reference Include="CSiAPIv1">
	    <HintPath>..\Program Files\Computers and Structures\ETABS 22\CSiAPIv1.dll</HintPath>
		<Private>false</Private>
		<ExcludeAssets>runtime</ExcludeAssets>
	  </Reference>
	</ItemGroup>

</Project>



6. With the modifications to the project file, you should be able to add a new Windows Form to your project. Name the form Form1 .



7. Edit the form using the Windows Forms Designer. The exact layout of the form is not important, but for the copy and paste code in the steps below to operate correctly, the form must be named Form1, and must contain a Button control named button1 and RichTextBox control named richTextBox1 .


8. Open the Form1.cs file and paste in the following code. Don't worry about compilation errors, you'll add the other required classes in the following steps.

using CSiAPIv1;
using Newtonsoft.Json;

namespace CSiNET8PluginExample1
{
    public partial class Form1 : Form
    {

        private cSapModel _sapModel;
        private cPluginCallback _pluginCallback;
        private int errorCode = 0; // default return code is no error

        public Form1()
        {
            InitializeComponent();
            FormClosing += Form1_FormClosing;
        }

        public void SetSapModel(ref cSapModel inSapModel, ref cPluginCallback inPluginCallback)
        {
            _sapModel = inSapModel;
            _pluginCallback = inPluginCallback;

            richTextBox1.Text = "Hello CSI Friends";
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // It is very important to call _pluginCallback.Finish(errorCode) when the form closes, !!!
            // otherwise, the CSI program will wait and be hung !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            _pluginCallback.Finish(errorCode);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                // Exercise some simple Newtonsoft.Json functionality
                string jsonText = JsonConvert.SerializeObject(richTextBox1.Text);
                string deserializedText = JsonConvert.DeserializeObject<string>(jsonText);
                
                CreateStructure(deserializedText);
            }
            catch (Exception ex)
            {
                MessageBox.Show("The following error occurred:" + Environment.NewLine + ex.Message);
                errorCode = 2; // will be visible to plugin end-user for debugging purposes
            }
            finally
            {
                this.Close();
            }
        }

        public void CreateStructure(string text)
        {
            int CallResult;
            int i;
            string FrameName = "FrameName";

            var aVectorFont = new cVectorFont();
            double CharHeight = 200.0;
            var HAlignment = cVectorFont.TextAlignment.kTA_HLeft;
            var VAlignment = cVectorFont.TextAlignment.kTA_VTop;
            double[] textX = new double[1], textY = new double[1];
            int NumPts;

            // test for exception handling
            if (string.Equals(text, "crash", StringComparison.InvariantCultureIgnoreCase))
            {
                textX[99] = 0; // out of bounds
            }

            aVectorFont.FillTextVertices(text.ToUpper(), CharHeight, HAlignment, VAlignment, ref textX, ref textY);
            NumPts = textX.Length;

            CallResult = _sapModel.InitializeNewModel();
            CallResult = _sapModel.File.NewBlank();
            for (i = 0; i < NumPts - 2; i++)
            {
                FrameName = "FrameName" + (i / 2).ToString();
                CallResult = _sapModel.FrameObj.AddByCoord(textX[i], textY[i], 0, textX[i + 1], textY[i + 1], 0, ref FrameName);
                i++;
            }

            _sapModel.View.RefreshView(0, false);
        }
    }
}
   


9. In your project, locate the Class1.cs file and rename it to cPlugin.cs . Then open it and paste in the following code. Please pay attention to the comments in the code, they contain important information about correct plugin operation.

using CSiAPIv1;

namespace CSiNET8PluginExample1
{
    // Implementing the cPluginContract interface is not required, however
    // it is recommended to ensure that the required cPlugin methods are created correctly.
    // Do not implement the Info or Main methods explicitly,
    // i.e. their method signatures are correct as is
    public class cPlugin : cPluginContract
    {
        private static string _modality = "Non-Modal";
        private static string _versionString = "2.0";
        private int errorCode = 0; // default return code is no error

        public int Info(ref string Text)
        {
            try
            {
                Text = "This plugin is supplied by Computers and Structures, Inc., " +
                       "as a simple example for developers of new plugins for CSI products. " +
                       "It starts a new blank model, then converts a line of text into " +
                       "frame objects and adds them to the model. It trivially uses the popular " +
                       "Newtonsoft.Json library to demonstrate proper dependency management. " +
                       "If you enter the " +
                       "text \"crash\", an error will be generated for testing purposes. " +
                       "Version " + _versionString;
            }
            catch (Exception)
            {
            }

            return 0;
        }

        public void Main(ref cSapModel sapModel, ref cPluginCallback pluginCallback)
        {
            var aForm = new Form1();

            try
            {
                aForm.SetSapModel(ref sapModel, ref pluginCallback);

                if (string.Compare(_modality, "Non-Modal", true) == 0)
                {
                    // Non-modal form, allows graphics refresh operations in CSI program, 
                    // but Main will return to CSI program before the form is closed.
                    aForm.Show();
                }
                else
                {
                    // Modal form, will not return to CSI program until form is closed,
                    // but may cause errors when refreshing the view.
                    aForm.ShowDialog();
                }

                // It is very important to call pluginCallback.Finish(errorCode) when the form closes, !!!
                // otherwise, the CSI program will wait and be hung !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                // This must be done inside the closing event for the form itself, not here !!!!!!!!!!!!!!

                // If you have only algorithmic code here without any forms, 
                // then call pluginCallback.Finish(errorCode) here before returning to the CSI program

                // errorCode = 0 indicates that the plugin completed successfully
                // ie pluginCallback.Finish(0)
                // errorCode = (Any non-zero integer) indicates that the plugin closed with an error
                // ie pluginCallback.Finish(1)
                // If an error occurs, the errorCode value will be displayed
                // to the plugin end-user in a message box, for debugging purposes.

                // If your code will run for more than a few seconds, you should exercise
                // the Windows messaging loop to keep the program responsive. You may 
                // also want to provide an opportunity for the user to cancel operations.

            }
            catch (Exception ex)
            {
                errorCode = 1;
                MessageBox.Show("The following error terminated the plugin:" + Environment.NewLine + ex.Message);

                // call Finish to inform the CSI program that the plugin has terminated
                try
                {
                    pluginCallback.Finish(errorCode); // error code 1 will be visible to plugin end-user for debugging purposes
                }
                catch (Exception)
                {
                }
            }
        }
    }
}