Direct Manipulation API, part 4: Communication

Updated: 7 Feb 2024

The creation feature and manipulation feature must at times exchange information with the plugin. The input system introduced so far can only give and modify input data for the plugin, but to exchange information with a feature requires more effort.

To start things off, we must first discuss how plugins store their respective data.

How plugins store data

When a plugin is made for Tekla Structures, there is usually some kind of internal information, that is not necessarily exposed through the instance to the system but rather through an external data structure. In our example we could simply call it PluginData. The important thing about this data structure is that it is mainly made up of data fields that use the Tekla.Structures.Plugins.StructuresFieldAttribute attribute to decorate the data to name the fields.

To give an example, let us continue with the example given in earlier parts of the Direct Manipulation API guide to present this idea.

Code example 6
namespace MyPluginNamespace
{
    public class PluginData
    {
        [StructuresField(PluginPropertyNames.Name)]
        public string partName;
        [StructuresField(PluginPropertyNames.Profile)]
        public string profile;
        [StructuresField(PluginPropertyNames.Offset)]
        public double offset;
        [StructuresField(PluginPropertyNames.Material)]
        public string material;
    }
    public struct PluginPropertyNames
    {
        public const string Name = "name";
        public const string Profile = "profile";
        public const string Offset = "offset";
        public const string Material = "material";
    }
}

This data structure now defines four different named fields for the plugin. On the plugin side, the data could be consumed like so:

Code example 6 continued
namespace MyPluginNamespace
{
    // Any using directives.
    [Plugin(MyPluginName)]
    [PluginUserInterface("MyPlugin.MyUserInterface")]
    public class MyPlugin : PluginBase
    {
        // Any other private fields.
        private string partName = string.Empty;
        private string profile = string.Empty;
        private string material = string.Empty;
        private double offset = 0.0;
        public const string MyPluginName = "MyPluginExample";
        // Initialize the Data property in the constructor.
        public MyPlugin(PluginData data)
        {
            this.Data = data;
        }
        // The actual data object.
        private PluginData Data { get; set; }
        public override bool Run(List<InputDefinition> Input)
        {
            try
            {
                this.GetValuesFromDialog();
 
                // Use the data to make the plugin component.
            }
            catch (Exception Exc)
            {
                MessageBox.Show(Exc.ToString());
            }
 
            return true;
        }
        public override List<InputDefinition> DefineInput()
        {
            // Define input here.
        }
        // Get the values from the data object here.
        private void GetValuesFromDialog()
        {
            this.partName = Data.partName;
            this.profile = Data.profile;
            this.material = Data.material;
            this.offset = Data.offset; 
            if (IsDefaultValue(this.partName))
                this.partName = "TEST";
            if (IsDefaultValue(this.profile))
                this.profile = "HEA200";
            if (IsDefaultValue(this.material))
                this.material = "STEEL_UNDEFINED";
            if (IsDefaultValue(this.offset))
                this.offset = 0;
        }
    }
}

The important thing to note here is that the plugin is using the data object to set all the necessary private fields to run the Run() method properly. Therefore, it should only be necessary to obtain a reference to this data object and run the plugin code again to make the necessary changes.

This is, in a nutshell, the crux of the communication flow between the features and the plugin. 

Communication in features

Let us look at some examples of use to become more familiar with the patterns of communication in the features. In Part 2: Contextual Toolbar we introduced the idea of creating controls for the feature.

Let us now set up a way for the feature to communicate with the plugin using the contextual toolbar:

Code example 7
namespace MyPluginFeatures
{
    // Any other using directives.
    using System.Linq;
    using MyPluginNamespace;
 
    public sealed class MyPluginManipulationFeature : PluginManipulationFeatureBase
    {
        // The component offset.
        private double offset = 0.0;
 
        /// Value box that contains the value of the offset.
        private ValueBoxControl offsetValueBox;
 
        // Rest of the class as before.

        protected override void DefineFeatureContextualToolbar(IToolbar toolbar)
        {
            this.offsetValueBox = toolbar.CreateValueTextBox();
            this.offsetValueBox.Tooltip = "Top radius";
            this.offsetValueBox.Title = "offset=";
            this.offsetValueBox.StateChanged += (control, eventArgs) =>
            {
                foreach (var component in this.Components)
                {
                    this.ModifyComponent(component, PluginPropertyNames.Offset, this.offsetValueBox.Value);
                }
            };
        }
 
        protected override void Refresh()
        {
            this.GetCurrentValues(this.Components.First());
 
            // Rest of the method.
        }
  
        // Rest of the class as before
 
        private void GetCurrentValues(Component component)
        {
            if (!(component.Select() && component.GetAttribute(PluginPropertyNames.Offset, ref this.offset)))
            {
                this.offset = 0.0;
            }
 
            this.offsetValueBox.Value = this.offset;
        }
 
        private void ModifyComponent(Component component, string key, double value)
        {
            if (component == null || component.Identifier.Equals(new Identifier()) || !component.Select())
            {
                return;
            }

            component.SetAttribute(key, value);
            component.Modify();
        }
    }
}

Using this same pattern, it is possible to communicate with the plugin data also from the creation feature.

For example, at the end of a picking session, the PickSessionEnded event is invoked. In the Contextual Toolbar control, we can set up a default value that can be modified by the user and which gets set in the data object before committing the input data:

Code example 8
namespace MyPluginFeatures
{
    // Any other using directives.
 
    using System.Collections;
    using System.Linq;
 
    using MyPluginNamespace;
 
    public sealed class MyPluginCreationFeature : PluginCreationFeatureBase
    {
        // Any other private fields.
 
        private readonly InputRange inputRange;
        private ValueBoxControl offsetValueBox;
        private PickingTool pickingTool;
 
        public MyPluginCreationFeature()
            : base(MyPlugin.MyPluginName)
        {
            this.inputRange = InputRange.AtMost(2);
        }
 
        // Constructor and Properties
 
        protected override void DefineFeatureContextualToolbar(IToolbar toolbar)
        {
            this.offsetValueBox = toolbar.CreateValueTextBox(0.0);
        }
 
        private void OnPickEnded(PickingTool sender)
        {
            var input = new ComponentInput();
            input.AddInputPolygon(new Polygon { Points = new ArrayList(this.pickedPoints) });
            this.Component.SetAttribute(PluginPropertyNames.Offset, this.offsetValueBox.Value);
            this.CommitComponentInput(input);
        }
    }
}

With all this, we have a way to convey information from the creation feature to the manipulation feature using the plugin data object.

These methods may seem simple but they are powerful tools for creating easy-to-use Tekla Structures plugins with Direct Manipulation creation and manipulation features.

 

What do you think about the Direct Manipulation API prototype?

Post your feedback or questions on the Tekla Open API discussion forum.

 

Was this helpful?
The feedback you give here is not visible to other users. We use your comments to improve the content.