LV RU EN
Developing a plugin for Tekla
2017/01/04   Raivis Spēlmanis, Valts Silaputniņš

Developing plugins for 3d modeling software Tekla Structures (TS) turned out to be tougher than we initially expected. Although TS provides Open API interface which could be easily integrated into .NET application, dealing with plugin infrastructure and API specifics took considerable effort.

Hello world project

We started with the task which seemed like a Hello world level challenge. We wanted to pick one surface of panel and cut set of grooves like in the image below:

Let's fix the sizes and shapes of grooves the for simplicity and keep only 2 input parameters: distance from edge and groove length.

Basics: PluginBase, PluginFormBase, StructuresData

Let's write some code. We will need:

  1. Plugin runtime class, inherited from PluginBase
  2. Plugin form class, inherited from PluginFormBase
  3. StructuresData class : some kind of transport class to pass data from form to plugin runtime

Plugin form code is quite simple - we only have to set default values for parameters and bind our standard buttons to Tekla standard functionality (Apply, Modify, Get, etc.) defined in PluginFormBase. The hard part is understanding when, why and what this standard functionality does :)


namespace GroovePlugin
{
    public partial class GroovePluginForm : PluginFormBase
    {
        public GroovePluginForm()
        {
            InitializeComponent();
        }

        //Use default values for plugins in models without a standard attribute file
        protected override string LoadValuesPath(string fileName)
        {
            SetAttributeValue(txtLength, 1800);  // One line for each plugin attribute
            SetAttributeValue(txtPosition, 80);
            string Result = base.LoadValuesPath(fileName);
            Apply();
            return Result;
        }

        //Remember to assign these events to the buttons.
        private void okApplyModifyGetOnOffCancel1_ApplyClicked(object sender, EventArgs e)
        {
            this.Apply();
        }

        private void okApplyModifyGetOnOffCancel1_CancelClicked(object sender, EventArgs e)
        {
            this.Close();
        }

        private void okApplyModifyGetOnOffCancel1_GetClicked(object sender, EventArgs e)
        {
            this.Get();
        }

        private void okApplyModifyGetOnOffCancel1_ModifyClicked(object sender, EventArgs e)
        {
            this.Modify();
        }
    

Much more happens in Plugin runtime class that's inherited from PluginBase. Besides plugin's mandatory attributes and bindings with user interface form we have to define StructuresData transport class if we want to get something from our user interface:


namespace GroovePlugin
{
    public class StructuresData
    {
        [StructuresField("Length")]
        public double Length;
        [StructuresField("Position")]
        public double Position;
    }

    /// 
    /// This is the same example found in the Open API Reference PluginBase section,
    /// but implemented using Windows Forms. The plugin asks the user to pick two points.
    /// The plug-in then calculates new insertion points using a double parameter from the
    /// dialog and creates a beam.
    /// 
    [Plugin("GropePlugin")] // Mandatory field which defines that the class is a plug-in-and stores the name of the plug-in to the system.
    [PluginUserInterface("GroovePlugin.GroovePluginForm")] // Mandatory field which defines the user interface the plug-in uses. A windows form class of a .inp file.
    public class GroovePlugin : PluginBase
    {
        private StructuresData Data { get; set; }

        private double cLenght;
        private double cPosition;

        // The constructor argument defines the database class StructuresData and set the data to be used in the plug-in.
        public GroovePlugin(StructuresData data)
        {
            TSM.Model M = new TSM.Model();
            Data = data;
        }

It's critical to set custom attributes: AttributeName and AttributeTypeName for every form control we want to use as an input. It may seem like quite a bit of overhead for a mainstream C# developer, but it's worth the effort as some useful magic may happen later.

Input

Every plug-in is required to implement DefineInput() method. This method should be implemented in the class that is decendant of the PluginBase. Tekla Structures will call this method to allow plug-in to initiate object picking.


//Defines the inputs to be passed to the plug-in.
        public override List<InputDefinition> DefineInput()
        {
            // Queries user to manually pick an object from Tekla Structures
            Picker picker = new Picker();
            List<InputDefinition> inputList = new List<InputDefinition>();
            // Pick a single object; Execution stops here until tekla returns the picked object
            TSM.ModelObject o1 = picker.PickObject(Picker.PickObjectEnum.PICK_ONE_OBJECT);
            // Store picked object
            InputDefinition Input1 = new InputDefinition(o1.Identifier);
            inputList.Add(Input1);
            // Return picked object list back to Tekla
            return inputList;
        }

When this is complete it will return list of the picked objects back to Tekla.

Run

This is where all the action happens. Tekla Structures will call this after the DefineInput() has completed or when existing object is changed into model. It means that Run will fire in case of new object creation and also when editing is done (existing object is picked and Modify button is pressed). So Run code will handle both create and edit functionality. In both cases InputDefinition is provided from Tekla runtime.


  //Main method of the plug-in.
        public override bool Run(List<InputDefinition> Input)
        {
            try
            {
                // Get selected object Id
                Tekla.Structures.Identifier id = ((InputDefinition)Input[0]).GetInput() as Tekla.Structures.Identifier;

                // Get surface object from model using Id
                TSM.Model myModel = new TSM.Model();
                TSM.SurfaceObject selectedSurface = myModel.SelectModelObject(id) as TSM.SurfaceObject;
                selectedSurface.Select();
                // Finally start doing some actual work :)
                AddGroove(selectedSurface);
            }
            catch (Exception Ex)
            {
             // Handle errors
            }

            return true;
        }