diff --git a/STPluginsExample/.ginignore b/STPluginsExample/.ginignore
new file mode 100644
index 0000000..2e9693e
--- /dev/null
+++ b/STPluginsExample/.ginignore
@@ -0,0 +1,2 @@
+obj
+bin
\ No newline at end of file
diff --git a/STPluginsExample/.vscode/launch.json b/STPluginsExample/.vscode/launch.json
new file mode 100644
index 0000000..cb85f30
--- /dev/null
+++ b/STPluginsExample/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": ".NET Core Launch (console)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ "program": "${env:SprutCAMDir}/sc.exe",
+ "args": [],
+ "cwd": "${workspaceFolder}",
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/STPluginsExample/.vscode/tasks.json b/STPluginsExample/.vscode/tasks.json
new file mode 100644
index 0000000..a31cdb1
--- /dev/null
+++ b/STPluginsExample/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/STPluginsExample.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/STPluginsExample.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/STPluginsExample.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/STPluginsExample/CAMPluginsFactory.cs b/STPluginsExample/CAMPluginsFactory.cs
new file mode 100644
index 0000000..a4ebbec
--- /dev/null
+++ b/STPluginsExample/CAMPluginsFactory.cs
@@ -0,0 +1,161 @@
+namespace SprutCAMPlugins; //Do not rename this namespace!
+
+using SprutTechnology.STLibraryTypes;
+using System.Collections.Generic;
+using System.Reflection;
+
+///
+/// Plugin class attributes.
+/// The PluginID and PluginType attributes are required for plugin class instances.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class SprutCAMPluginAttribute: System.Attribute
+{
+ public string PluginID { get; set; } = Guid.Empty.ToString();
+ public string PluginType { get; set; } = Guid.Empty.ToString();
+ public string PluginCaption { get; set; } = "";
+ public string PluginDescription { get; set; } = "";
+}
+
+///
+/// Base class for registering and calling plugins.
+///
+public class CAMPluginsFactory : IST_CAMPluginsFactory
+{
+ public CAMPluginsFactory()
+ {
+ RegAllPlugins();
+ }
+
+ private void RegAllPlugins()
+ {
+ var a = Assembly.GetExecutingAssembly();
+ foreach (TypeInfo c in a.DefinedTypes.Where(t => t.IsClass && t.IsPublic)) {
+ var atr = c.GetCustomAttribute(typeof(SprutCAMPluginAttribute));
+ if (atr != null) {
+ var dsc = (SprutCAMPluginAttribute)atr;
+ TPluginsEnumerator.Plugins.Add(new PluginDescriptor(
+ new Guid(dsc.PluginType),
+ new Guid(dsc.PluginID),
+ c.AsType(),
+ dsc.PluginCaption,
+ dsc.PluginDescription
+ ));
+ }
+ }
+ }
+
+ /// IST_CAMPluginsFactory
+ public IST_CAMPluginsEnumerator GetPluginsEnumeratorOfType(Guid PluginInterfaceID)
+ {
+ return new TPluginsEnumerator(PluginInterfaceID);
+ }
+
+ /// IST_CAMPluginsFactory
+ public IST_CAMPlugin CreateInstanceOfPlugin(Guid PluginID)
+ {
+ return TPluginsEnumerator.CreateInstanceOfPlugin(PluginID);
+ }
+}
+
+///
+/// The base abstract class of a plugins.
+///
+public abstract class TAbstractPlugin : IST_CAMPlugin
+{
+ protected PluginDescriptor fDescriptor;
+
+ public TAbstractPlugin()
+ {
+ foreach(var desc in TPluginsEnumerator.Plugins) {
+ if (desc.PluginClass.Equals(this.GetType())) {
+ fDescriptor = desc;
+ break;
+ }
+ }
+ }
+
+ public Guid PluginID => fDescriptor.PluginID;
+
+ public string PluginCaption => fDescriptor.PluginCaption;
+
+ public string PluginDescription => fDescriptor.PluginDescription;
+}
+
+///
+/// Basic Plugin Attributes Structure.
+///
+public struct PluginDescriptor
+{
+ public Guid PluginType;
+ public Guid PluginID;
+ public Type PluginClass;
+ public string PluginCaption;
+ public string PluginDescription;
+
+ public PluginDescriptor(Guid pluginType, Guid pluginID, Type pluginClass, string pluginCaption, string pluginDescription)
+ {
+ PluginType = pluginType;
+ PluginID = pluginID;
+ PluginClass = pluginClass;
+ PluginCaption = pluginCaption;
+ PluginDescription = pluginDescription;
+ }
+}
+
+///
+/// Basic enumerator for working with registered plugins.
+///
+public class TPluginsEnumerator : IST_CAMPluginsEnumerator
+{
+ int index;
+ Guid pluginType;
+
+ public static List Plugins = new List();
+
+ public TPluginsEnumerator(Guid PluginType)
+ {
+ index = -1;
+ pluginType = PluginType;
+ }
+
+ /// IST_CAMPluginsEnumerator
+ public bool MoveNext()
+ {
+ index++;
+ while (index < Plugins.Count)
+ {
+ PluginDescriptor dsc = Plugins[index];
+ if (dsc.PluginType == pluginType)
+ break;
+ index++;
+ }
+ return index < Plugins.Count;
+ }
+
+ /// IST_CAMPluginsEnumerator
+ public Guid GetCurrent()
+ {
+ if (index < Plugins.Count)
+ return Plugins[index].PluginID;
+ else
+ return Guid.Empty;
+ }
+
+ private static int IndexOfPlugin(Guid pluginID)
+ {
+ for (int i = 0; i < Plugins.Count; i++)
+ if (Plugins[i].PluginID == pluginID)
+ return i;
+ return -1;
+ }
+
+ public static IST_CAMPlugin CreateInstanceOfPlugin(Guid PluginID)
+ {
+ IST_CAMPlugin? result = null;
+ int i = IndexOfPlugin(PluginID);
+ if (i >= 0)
+ result = (IST_CAMPlugin)Activator.CreateInstance(Plugins[i].PluginClass)!;
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/STPluginsExample/Plugin1.cs b/STPluginsExample/Plugin1.cs
new file mode 100644
index 0000000..2d2fffa
--- /dev/null
+++ b/STPluginsExample/Plugin1.cs
@@ -0,0 +1,62 @@
+using SprutCAMPlugins;
+using SprutTechnology.STLibraryTypes;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace PluginsExample
+{
+ /// Plugin class example.
+ /// The PluginID attribute must be unique.
+ /// The PluginType attribute must match the interface GUID, implemented in the plugin class.
+ /// All plugin classes must inherit from the TAbstractPlugin base class
+ /// and implement the IST_UtilitiesButtonCAMPlugin interface
+ /// (plugin interfaces may differ in their purpose).
+ [SprutCAMPlugin(
+ PluginID = "7581E851-9239-4820-A4DF-FBBADCAAD7DD",
+ PluginType = "4B74BB21-9F48-4D62-9870-0A831C8AD2DA",
+ PluginCaption = "Plugin 1 caption",
+ PluginDescription = "Plugin 1 description"
+ )]
+ public class TPlugin1Class : TAbstractPlugin, IST_UtilitiesButtonCAMPlugin
+ {
+ /// IST_UtilitiesButtonCAMPlugin
+ /// An event called when the plugin button is clicked in the SprutCAM client.
+ public void OnButtonClick(object SenderApplication)
+ {
+ //var app = (IST_Application) SenderApplication;
+ TUtils.ShowMessage("TPlugin1Class button click!", "PluginsExample", 0);
+ }
+ }
+
+ /// Plugin class example.
+ [SprutCAMPlugin(
+ PluginID = "462D862C-2095-41DA-B79A-54D424194EA5",
+ PluginType = "4B74BB21-9F48-4D62-9870-0A831C8AD2DA",
+ PluginCaption = "Plugin 2 caption",
+ PluginDescription = "Plugin 2 description"
+ )]
+ public class TPlugin2Class : TAbstractPlugin, IST_UtilitiesButtonCAMPlugin
+ {
+ // IST_UtilitiesButtonCAMPlugin
+ /// An event called when the plugin button is clicked in the SprutCAM client.
+ public void OnButtonClick(object SenderApplication)
+ {
+ //var app = (IST_Application) SenderApplication;
+ TUtils.ShowMessage("TPlugin2Class button click!", "PluginsExample", 0);
+ }
+ }
+
+ /// For example, not mandatory.
+ public class TUtils {
+
+ [DllImport("User32.dll", CharSet = CharSet.Unicode)]
+ public static extern int MessageBox(IntPtr h, string m, string c, int type);
+
+ public static int ShowMessage(string message, string caption, int msgType) {
+ IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
+ int res = MessageBox(handle, message, caption, msgType);
+ return res;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/STPluginsExample/ReadMe.md b/STPluginsExample/ReadMe.md
new file mode 100644
index 0000000..25d2cda
--- /dev/null
+++ b/STPluginsExample/ReadMe.md
@@ -0,0 +1 @@
+This solution is under development
\ No newline at end of file
diff --git a/STPluginsExample/STPluginsExample.csproj b/STPluginsExample/STPluginsExample.csproj
new file mode 100644
index 0000000..d159b66
--- /dev/null
+++ b/STPluginsExample/STPluginsExample.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0-windows
+
+ true
+ enable
+ CS8603
+
+
+
+
+
+
+
+
+