Writing A PowerMILL Plugin v6
Writing A PowerMILL Plugin v6
Revision No: 6
Contents
1. Introduction 4
3. A basic plugin 7
3.1. Prerequisites 8
3.2. Project setup 8
3.3. Adding required references 9
3.4. Implementing PowerMILL's plugin interface 11
3.5. Making the class accessible through COM 14
3.6. Registering the assembly 14
3.7. Supporting PowerMILL's plugin component category 15
3.8. Trying it out 15
3.9. Supporting a dialog 16
3.10. Handling commands 18
3.11. Issuing commands 19
3.12. Limitations 20
4. Event mechanism 21
4.1. Overview of events 21
4.2. Subscription to events 22
4.3. Filtering events 22
4.4. Event notifications 22
4.5. Unsubscribing from events 23
4.6. Obtaining a list of events 23
4.7. Finding out about recent events 23
9. Creating an installer 46
9.1. Visual Studio 46
9.1.1. Adding an installer project 46
9.1.2. Setting up the files to be installed 47
9.1.3. Configuring the installer properties 47
9.1.4. Prerequisites and launch conditions 48
9.1.5. Ensuring assembly is registration 49
9.1.6. Ensuring the component category is added 52
9.1.7. Adding custom actions 53
9.1.8. Building and testing the installer 54
9.2. Alternative installer programs 54
1. Introduction
This document describes how to write a plugin for PowerMILL, and create an
installer program for it. It assumes you are familiar with both PowerMILL and
programming in a .NET language. All the examples are in C#, although you could use
VB.
2.2.1. Panes
Panes reside within a dockable window within PowerMILL's main window, and is
usually docked to the right. A pane consists of a header region, managed by
PowerMILL, and the content managed by the plugin. The header contains an icon, a
title, and a maximise/minimise indicator. Panes stack up within a dedicated plugin
panel, which usually docks to the right of the graphics window.
PowerMILL gives the plugin an HWND so it can render the content of the pane, and
forwards any messages relating to that window, including move and resize
notifications.
A plugin can have multiple panes. If the panes don’t all fit within the available space,
PowerMILL automatically provides a scroll bar. The developer specifies the initial
order of a plugin's panes and users can reorder them.
Panes have a fixed height, defined by the developer, but the user can resize them
horizontally. The plugin can change the height of any of its panes at runtime. Plugins
must be able to handle resize notifications, and update their layout accordingly. You
can specify a minimum width for your pane, although PowerMILL enforces a small
minimum.
2.2.2. Tabs
Tabs reside within a dockable window within PowerMILL's main window, and is
usually docked to the bottom. A tab consists of a tab title region, which is managed by
PowerMILL and is used to select the tab, and the content, which is managed by the
plugin. The title may consist of an icon and/or text. Only one tab is visible at any one
time.
The mechanism for handling tabs is very similar to that for handling panes, although
they require the plugin to support different interfaces. A plugin can have multiple
tabs, and PowerMILL can support any number of plugins that use tabs.
Tabs will always scale to use the full width of the tab bar, which will be almost the
full width of the application when docked. The user can resize the tab bar vertically.
Plugins must be able to handle resize notifications, and update their layout
accordingly. You can specify a minimum height for your tab, although PowerMILL
enforces a small minimum.
2.2.3. Dialogs
Plugins can raise dialogs. PowerMILL provides the plugin with its main HNWD, so
they can correctly parent any new dialogs with the main application window. This
ensures the dialogs never 'vanish' behind the main window. You can create modal and
modeless dialogs.
The plugin does not have access to the main application's COM interface.
• When PowerMILL closes it ends all plugins as part of its shutdown sequence.
The plugin's installer program performs these actions (see §0). However, you must
perform these activities manually during the plugin development.
There are three methods that are called to pass additional information to the plugin:
Where:
A plugin can get a wealth of information about any PowerMILL entity using the
Project interface. For more information see §0.
When you disable a plugin, all references to the COM object are dropped, and the
plugin's CCW is destroyed. The actual plugin is destroyed next time the CLR garbage
collector runs. When you enable a plugin, a new COM object is created and re-
initialised.
• Enabling/disabling
• Commands issued
• Events posted.
The event log contains a 'First In First Out' list of the last 1000 events.
3. A basic plugin
This section explains how to create a basic plugin without using any pre-existing code
as a starting point. This simple example shows the fundamental mechanisms involved
and isn’t the recommended route. See §6 for a more sophisticated, and recommended
approach which uses a plugin framework assembly to develop a robust plugin.
3.1. Prerequisites
The instructions use:
You can create plugins using Visual C#/VB 2010 Express (the free versions).
However, creating an installer program to distribute the plugin isn’t possible using the
Express versions. For details about creating an installer using the professional version
of Visual Studio, see §9.1; for alternative installers see §9.2.
These instructions are based on .NET 4.0. You can use .NET 3.5, but some
instructions may vary slightly. To set the version of the framework:
1. Right click the project on the solution explorer, and select Properties.
2. Set the Target Framework to .NET Framework 4.0. This may issue a
warning message about needing to reopen the project.
Give this DLL a strong name. This means it’s created using a unique public/private
key pair, and the .NET framework guarantees it will load the exact same version of
the DLL that your plugin was built with. If PowerMILL loads two plugins, built with
separate versions of this DLL (for example, from different releases of PowerMILL)
and strong names aren’t used, only the first instance of the DLL encountered is loaded
into the process space. This is likely to lead to a crash.
Your path here may vary - please double check where the SDKs are
actually installed. For .NET 3.5, the path for 64-bit tools is usually :
2. Generate a strong name key file using the strong name tool sn.exe:
sn.exe -k key_pair.snk
3. Create the DLL from the type library using the /keyfile and /asmversion
options (use the correct version number for the version of PowerMILL that's
installed):
tlbimp.exe pmill.exe
.exe /keyfile:key_pair.snk /asmversion:13.1.04
The output file is called PowerMILL.dll ass no output file was specified.
specified
6. Select PowerMILL.dll.
7. To instruct Visual Studio to use the exact version, and not to look for a newer
version. Right click on the PowerMILL reference, and select Properties. Set
Specific Version to True.
8. If you're using .NET 4.0, set Embed Interop Types to True; this means
PowerMILL.dll will be embedded, so doesn't need to be installed seperately.
9. The example plugin needs a reference to System.Windows.Forms, and a
corresponding using statement at the top of the file:
using System.Windows.Forms; // For MessageBox
Visual studio automatically provides implementations for all methods and properties
within the interface. Certain arguments that exist within the type library are
transformed to compatible types within the CLR; for example, BSTR* becomes a
string. All methods and properties need to have their placeholder implementations
replaced, but many of these are straightforward.
Method/property Implementation
public string Author Return the name of the plugin's author.
public string Description Return a description of the plugin.
Ideally, translated to the current locale.
public void DisplayOptions() Raise the plugin's options form, if it
has one.
public bool HasOptions Returns true if the plugin has an
options form.
public void Initialise( Called to initialise the plugin. The
string Token,
PowerMILL.PluginServices pServices,
plugin must cache all of the arguments
int ParentWindow for later use. This isn’t called if the
) plugin isn't enabled.
public void MinPowerMILLVersion( Called to ascertain the minimum
out int pMajor,
out int pMinor,
supported version of PowerMILL.
out int pIssue
)
public string Name Return the name of the plugin. Ideally,
translated to the current locale.
public void PluginIconBitmap( Called to obtain the plugin's icon
out PowerMILL.PluginBitmapFormat format,
out byte[] pPixelData,
bitmap, as displayed within the plugin
out int pWidth, manager. This should be a 24 x 24
out int pHeight pixel bitmap serialised as an
)
uncompressed pixel byte array. You
can specify the pixel, although
BGRA32 is normal (and matches the
png format).
public void PreInitialise(string locale) Called to inform the plugin of the
locale. It is always called before
Initialise(), even if the plugin isn't
going to be enabled.
public void ProcessEvent(string EventData) Called with an XML snippet of event
data.
public void ProcessCommand (string Command) Called with a command, which the
plugin must parse and interpret.
public void SerializeProjectData( Called when a project is saved or
string Path,
bool Saving
loaded.
)
public void Uninitialise() Called immediately before a plugin is
Method/property Implementation
ended. The plugin must drop all
references to PowerMILL (or
dependant COM objects), and force an
immediate garbage collection, for
example by executing GC.Collect().
public void Version( Called to ascertain the version of the
out int pMajor,
out int pMinor,
plugin.
out int pIssue
)
public void Version(out int pMajor, out int pMinor, out int pIssue)
{
pMajor = 1;
pMinor = 0;
pIssue = 0;
}
At this stage, the plugin only raises a message if the user requests the options dialog
for the plugin.
• is accessed by native code, that knows how to create a .NET CLR object,
• marshal calls to it from native code,
• manages .NET garbage collection when the COM reference count drops to
zero.
3. Regenerate the GUID (Globally Unique Identifier) for each new plugin using
the command line tool uuidgen.exe (available within the Microsoft SDKs), or
the Visual Studio tool, Tools > Create GUID selecting 4. Registry Format
before pressing New GUID, but omit the curly braces at either end.
4. Create the DLL using Build > Rebuild Solution.
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\regasm.exe BasicPlugin.dll
/register /codebase
regasm.exe will issue a warning about the /codebase option being used not in
conjunction with a strong named assembly. It is safe to ignore this warning, although
giving your assembly a strong name will prevent the warning.
If you are using .NET 3.5, then you must use thee correct version or regasm.exe:
Microsoft.NET\Framework\v2.0.50727\regasm.exe.
C:\WINDOWS\Microsoft.NET Using an older
version of regasm.exe
.exe will result in an error, and PowerMILL will not be able to load
the plugin.
{311b0135-1826-4a8c
4a8c-98de-f313289f815e}
Where {6e90c6bf-ebe9-427f
427f-bffb-e883815ea72e} is the plugin'ss GUID (as defined
for the CCW in §3.5). Substitute /reg:32' with /reg:64 for x64 builds.
builds Add the key
for both x86 and x64 to allow the plugin to run with both build variants of
PowerMILL.
1. To add a dialog, right click on the project and select Add > New item....
2. Select Visual C# templates > WPF > User Control (WPF) and name it
BasicForm.
Visual Studio doesn't offer a Window (WPF) template. To convert the automatically
generated UserControl into a Window:
1. Change the opening and closing xml tags from UserControl to Window in the
XAML file.
2. Change the parent class in the code behind file to Window.
To add a title to the dialog, and to specify the initial dimensions, add these attributes
within the main Window element of the XAML file.
Some of the unnecessary namespace definitions and designer attributes have been
removed.
<Window x:Class="BasicPlugin.BasicForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400"
Height="200"
Title="Basic Plugin Form">
In the main plugin class, make a few changes to create the dialog and a response to a
PowerMILL command.
This gives the plugin the ability to raise a dialog, but the method isn't called.
As detailed in §2.5, each plugin can receive commands that start with the token
PLUGIN followed by the plugin's GUID. The command you'll issue within PowerMILL
is:
PowerMILL parses the overall command, and passes just the contents of the line
following the plugin's GUID. In this example, the plugin receives:
RAISE BASIC_FORM
You must modify your version of ProcessCommand() to handle this command. You'll
use regular expressions to make the match robust, although a simple string
comparison works in most circumstances. Add the following namespace:
using System.Text.RegularExpressions; // For Regex
Rebuild the plugin using Visual Studio, run PowerMILL again, and issue the
command to raise the dialog.
You don’t need to re-register the assembly or add the component category.
You must implement a function of the specified name in the code behind the file for
the Click event handler. Right
R click on the attribute inn the XAML file, and select
Navigate to Event Handler:
Handler
This generates:
private void Button_Click(object
Button_Click( sender, RoutedEventArgs e)
{
• The token given to the plugin on initialisation,, which is used to identify the
plugin when communicating with PowerMILL,
• The
he plugin services interface.
interface
The modifications required to supply these members when constructing the object are:
2. Modify the constructor to take the token and services object as arguments, and
cache the values:
public BasicForm(string token,
PowerMILL.PluginServices services)
{
m_token = token;
m_services = services;
InitializeComponent();
}
3. Pass the token and services object to the constructor when creating the form in
BasicExamplePlugin.RaiseForm():
3.12. Limitations
This section has outlined the basic concepts behind creating a plugin, registering it for
use, adding some GUI, and supporting commands in both directions between the
plugin and main application. However, a 'professional quality' plugin needs the ability
to:
It is possible to support all of these concepts by continuing to build on the code you've
already developed in this section, but it would be required for each plugin. It is
preferable to build plugins on top of a framework. This is described in §6, and should
substantially reduces the amount of code required to create a plugin.
4. Event mechanism
4.1. Overview of events
When certain key events occur within PowerMILL, notifications are broadcast to any
plugin that has registered an interest in that event. For example, a plugin can request
to hear about any entity activations. When an entity is activated it calls a method on
the plugin to report the event, and supply any relevant information. Plugins receive
events as snippets of XML through a single plugin method, so, for instance, the entity
activated event is:
The event_group attribute helps to 'triage' incoming events, and pass them to the most
appropriate handling function within the plugin.
The unique_id attribute helps distinguish between events. For each event that occurs
in PowerMILL, an internal counter is incremented, and the current value of this
counter is passed to the event. However, if multiple subscriptions exist for the same
event, they will all share the same unique_id because they all relate to the same thing.
The unique_id can be used to determine the order in which related events occur too.
The table shows the sort of events PowerMILL supports. See §4.6 for a full list.
Currently, there are approximately 60 events PowerMILL supports.
For a plugin to register interest in an event, it calls a method on the services interface
with the identifying token and the name of the relevant event. For example:
int subscription_id = m_services.SubscribeToEvent(m_token, "EntityActivation");
PowerMILL responds by returning a subscription ID. You should cache this, as it can
be used to unsubscribe from this event when the plugin no longer needs it.
For instance, the plugin may only be interested in toolpath activations, but the normal
event subscription means the plugin is notified about all entity activations. You can
specify that you only want notifications of EntityActivatation events where the
EntityType field contains Toolpath:
Parsing the XML snippet is the responsibility of the plugin, although the framework
(see §6.9) provides a mechanism to do this. The advantage of using XML to broadcast
events is that additional events and arguments can be added to PowerMILL without
breaking the type library. Events and arguments are unlikely to ever be removed.
where the last integer specifies the index of the event, with 0 indicating the latest, 1
indicating the previous, etc.. PowerMILL currently keeps the latest 15 events of each
type on a rolling basis, although the exact number is not guaranteed.
This functionality is useful to hear about things that occurred before the plugin was
initialised, and so allows the plugin to, for example, synchronise its GUI with the
current state of PowerMILL.
The framework builds into a self-contained strong named assembly you can include as
a reference DLL to the plugin project. The framework is also available as a Visual
Studio Project, which can be included in the Visual Studio Solution.
You can reference the framework DLL with Visual Basic Express, but you can't
include the project because it's C# and the Express editions don't allow a mix of
languages.
If you include the framework project, it is important to sign it with a strong name and
reference this specific version, otherwise your plugin may use an already-loaded (but
potentially incompatible) version of the assembly from a different plugin, leading to a
crash.
In this example, you add the framework as a project to the solution (as opposed to a
DLL reference).
1. Right click the solution in the explorer, and select Add > Existing Project....
6. The plugin project needs the framework project adding as a reference. Right
click the plugin project, and select Add Reference....
7. The Project tab contains a single item. Select this item and click Accept.
Properties Description
string Token Returns the token used when
communicating with PowerMILL.
PluginServices Services Returns the interface used to
communicate directly with PowerMILL.
IntPtr? ParentWindow Returns a reference to PowerMILL's
main window. This is essentially a
HWND. Note that it's a nullable type.
EventManager EventUtils Returns a reference to the event manager
used to subscribe to events.
string PluginAssemblyName Returns the plugin's assembly name.
Guid PluginGuid Returns the plugin's GUID.
string PluginInstallPath Returns the plugins full install path.
TranslationManager TranslationUtils Returns the plugin's translation manager.
Properties Description
bool ConnectedToPowerMILL Returns true for both of the 'normal' base
classes, but false for the version of the
base class that's designed for testing the
plugin separately from PowerMILL.
Properties Description
void issue_command(string command) Called to issue a normal PowerMILL
command, which fails if PowerMILL
is busy. The function won’t return until
the command has been processed or
failure occurred.
void insert_command(string command) Called to issue a command by adding it
to PowerMILL's command queue. The
function call returns before the
command is processed if the queue
isn’t empty. This function fails if
PowerMILL is busy unless it's in
response to a plugin command (see
§3.10).
string get_string_par(string par_name) Functions to obtain, parse and convert
double get_real_par(string par_name)
parameter values into basic types.
int get_int_par(string par_name)
These functions work even if
commands are echoed.
string get_pmill_info(string info_name) Returns a named piece of information
(see §5).
Visual Studio provides a quick and easy means of implementing all the required
overrides for the base class. Right click on the parent class name, and select
Implement Abstract Class.
Method/property Implementation
void register_panes() Called at the relevant time to give the plugin
opportunity to register panes with the parent class.
string PluginName Returns the name of the plugin. Ideally translated to
the current locale.
string PluginAuthor Returns the name of the plugin's author or company.
string PluginDescription Returns a description of the plugin. Ideally
translated to the current locale.
string PluginIconPath Returns a path to the plugin's icon resource.
Version PluginVersion Returns the version of the plugin.
bool PluginHasOptions Returns true if the plugin has an options dialog. If
so, also override the virtual function
void DisplayOptionsForm();
Version PowerMILLVersion Returns the minimum version of PowerMILL that
supports the plugin.
string PluginAssemblyName Returns the name of the plugin's assembly.
Guid PluginGuid Returns the plugin's GUID.
• An enum describing how the colour information of the pixel is ordered (the
three options are BGRA32, BGRA24, RGB24).
• A pixel array containing all the pixel information.
• The dimensions of the image.
The size of the pixel array is a function of the pixel format and the dimensions. For 4-
channel images (3 colours plus alpha), the array contains 4 bpp (bytes per pixel), and
without the alpha channel, there are 3 bpp. The size of the array is bpp x width x
height.
The base class uses a utility function (defined within the framework) to take a png
resource file, and create an array that meets the specification. The override property
PluginIconPath returns a path to the png file within the resources. This example
returns "Images/plugin_icon.png". To add a bitmap to the project:
1. Create an Images directory by right clicking on the project, and selecting Add
> New Folder,, and changing the name.
reate a suitable png file with a transparent background that's 32 x 32 pixels,
2. Create
and call it "plugin_icon.png" (or whatever you returned for PluginIconPath
PluginI ).
This
his example uses
use the flag icon .
3. Add the png file to the project by right clicking on the image folder in the
solution explorer, and selecting Add > Existing Item....
fil to Image Files, select the png file, and accept the
4. Change the file name filter
dialog.
5. Right click on the image in the solution explorer, select a Build Action of
Resource. The Copy to Output Directory property can be set to Do not
copy.
You need to create a pane,, and register it with the base class:
2. Select Visual C# templates > WPF > User Control (WPF) and name it
ExamplePluginPane
ExamplePluginPane.
This function:
Inn several places the text is surrounded by > ... < or >>...<< brackets.
brackets This is a
warning that it couldn’t find a translation file, or couldn't locate the translation within
the file, so localisation failed. See §6.7 for details on localisation.
The procedure for supporting tabs is almost identical to that for panes, so no worked
example is given. However, a basic implementation might look like this:
protected override void register_tabs()
{
ExamplePluginTab tab = new ExamplePluginTab();
register_tab(
new TabDefinition(
tab,
150, // Min Height
TranslationUtils.Translate("Example tab"),
"Images/tab_icon.png"
)
);
}
6.8. Localisation
6.8.1. Overview
The framework contains a simple localisation mechanism, so you can distribute
plugins globally. If your plugin doesn’t require translation into other languages, there
is no need to use the mechanism provided. If you have access to another localisation
mechanism, your plugins can use that instead of the one supplied with the framework.
For each phrase that needs translation, a <message> element exists, containing one
sub-child with the <english> and another with the <translation>.
A 'special' xml file is also supported, 'check.xml'. This file contains <translation>
nodes that are identical to the English phrase except they're pre-pended with the letter
'X'. This makes it is quick and easy to automatically generate such a file, allowing a
simple test to ensure all phrases are translatable. PowerMILL supports the 'check'
language.
Usually, the en-GB.xml file contains identical elements for the <english> and
<translation> nodes. This isn’t necessary - the <english> node is really only a 'key'.
It is sometimes advantageous for this key to differ from the translation even in the
en-GB.xml file. Reasons include:
This instructs the XAML interpreter where to locate the translation markup
extension.
3. The 'root' element of any controls that require translation must support the
ITranslationSupporter interface. To do this, modify the XAML code-behind
file:
using Delcam.Plugins.Framework;
using Delcam.Plugins.Localisation;
namespace PluginUsingFramework
{
public partial class ExamplePluginPane : UserControl, ITranslationSupporter
{
private IPluginCommunicationsInterface m_comms;
http://www.wpftutorial.net/LocalizeMarkupExtension.html
• He uses keys within the XAML, not English phrases. This makes it more
difficult to read the XAML, as you've got to refer to another file to obtain the
actual string.
• The keys and translations are held in a ResX format file, whereas we're using a
XAML files. The XAML files used are compatible with other Delcam
products.
• Subscribing to events.
• Converting the event's notification (an XML snippet) into a dictionary of
arguments, and calling an appointed delegate function.
• Unsubscribing when event notifications are no longer required.
Modifying the example plugin pane to list all the active entities within PowerMILL
shows you how to subscribe to events.
To subscribe to an event, create EventSubscription objects that take the name of the
event you're subscribing to, and a function delegate of type EventCallback. These
objects are registered using the event manager. Add this to the pane's constructor
(within the XAML code-behind file) at the end of the function:
m_comms.EventUtils.Subscribe(
new EventSubscription("EntityActivated", ListenActivation)
);
m_comms.EventUtils.Subscribe(
new EventSubscription("EntityDeactivated", ListenDeactivation)
);
ListenActivation and ListenDeactivation are the function delegates that are called
when the event occurs. Add the code-behind file:
List<string> m_active_entities = new List<string>();
}
UpdateEntityList();
}
void UpdateEntityList()
{
m_active_entities.Sort();
StringBuilder sb = new StringBuilder();
foreach (string entity in m_active_entities)
{
sb.AppendLine(entity);
}
PaneText.Text = sb.ToString();
}
• ListenActivation() extracts the entity type and entity name from the event
arguments, constructs a string and adds it to the list.
• ListenDeactivation() is very similar, but it removes items from the list.
• Both functions call UpdateEntityList() to rebuild the text box on the pane.
A method exists to take a command string, and break it down into discrete tokens
ready for parsing, taking care with quoted strings:
List<string> tokens = CommandUtils.GetCommandTokens(command);
All non-string tokens are capitalised, and any white space removed. A plugin then
loops over the tokens, extracting meaning depending on the context. For example, the
command:
Is passed to the plugin as a string 'DIAMETER 5.5'. This is broken down into two
tokens 'DIAMETER' and '5.5'.
There are six distinct functions to remove a token from the list and parse it.
Method Description
string GetStringToken( Removes the first token from the list, and
ref List<string> tokens)
returns it.
string GetDoubleQuotedStringToken( Removes the first token from the list, removes
ref List<string> tokens)
leading or trailing " marks, and returns it.
double? GetRealToken( Removes the first token from the list, parses it
ref List<string> tokens)
as a double, and returns it. If the parse fails,
the returned value is null.
int? GetIntToken( Removes the first token from the list, parses it
ref List<string> tokens)
as an int, and returns it. If the parse fails, the
returned value is null.
bool? GetBoolToken( Removes the first token from the list, parses it;
ref List<string> tokens)
ON, YES or TRUE all equate to true, OFF,
NO or FALSE all equate to false, and
anything else results in null.
Color? GetColourTokens( This calls GetIntToken() three times,
ref List<string> tokens)
expecting three ints in the range 0 - 255, and
attempts to create a Color object from the
result. Returns null on failure.
• You can’t debug plugins using Visual Studio when running within
PowerMILL's process space.
• PowerMILL takes several seconds to start up, whereas a plugin on its own can
start in a fraction of a second.
• Having a test harness wrapped around a plugin means you can simulate the
input from PowerMILL for testing purposes.
Where this isn't possible, another means exists that will allow you to run the
plugins outside of PowerMILL's process space, allowing them to be debugged - see
§7.3.
Only one project within the solution is nominated as the 'startup' project. This is the
assembly that Visual Studio starts if the user tries to Start Debugging. Normally this
is the plugin itself, which is a DLL and therefore can't be started directly. To change
the startup project to be the new host project:
1. Right click the solution in the explorer tree, and select Properties.
2. Under the Single startup project, select the host project and accept the
dialog.
3. Press the Play button to start debugging.
3. Modify the constructor to create a version of the framework that doesn't rely
on PowerMILL running. This is PluginFrameworkNoPowerMILL, and takes the
plugin's GUID and the assembly name as arguments. This framework is
passed to the pane when it's constructed, and the pane is set to be the content
of the window.
public MainWindow()
{
InitializeComponent();
The plugin's main pane is now the content for the host application's window, and you
can launch the application. However, this results in an exception:
Two installers exist to install this surrogate application: one each for x86 and x64
versions of PowerMILL, and can be found under
dcam/files/plugins/installers/surrogate. You only need to install one of these
surrogates, but be aware that the version of the surrogate that's installed affects the
version of plugin that's used. If the surrogate installed is x86, and the plugin is
registered x64, it won't work - irrespective of whether PowerMILL is running x86 or
x64. You can install both versions of the surrogate at the same time to avoid any
issues.
Once the surrogate is installed, raise the plugin manager form; a new checkbox should
be available at the bottom to enable the surrogate. Check this, and accept the warning
that it won't take effect until PowerMILL is restarted.
After PowerMILL is restarted, a warning should appear on the plugin manager form
that the surrogate is running.
Certain operations may not be as 'slick' using the surrogate, such as repaint behaviour
of panes and tabs, and an overall slowdown in communication.
Using the surrogate makes debugging possible - however, the plugin can't be started
using the debugger, because PowerMILL still needs to instigate its creation. The
debugger needs to be attached to the running plugin. The easiest means of doing this
is to implement a new plugin command, which calls:
System.Diagnostics.Debugger.Break();
When the plugin executes this code, a 'Just In Time' debug window will be raised,
allowing a debugger to be attached, and breakpoints etc. can then be used in the
normal way.
Methods/Properties Description
Methods/Properties Description
their parameters.
int CheckPAF(string ProductName, Allows a plugin to check the status of a
out int pStatus,
specific PAF code.
out int pNumDays)
Bool Busy Returns true if PowerMILL is busy.
void CommandWindowMessage( Send a message to the command window.
string message)
void QueueCommand(string Token, Queue a command, and don't wait for it
string Command)
to execute. PowerMILL can be busy
because it's not being asked to process the
command immediately.
string GetParameterValue( Get a parameter value without needing to
string Token,
issue a command. This is useful because
string ParameterName)
it can be called in response to an event,
where PowerMILL, is by definition,
busy. This produces a result similar to
the PRINT PAR command; consider
using GetParameterValueTerse() if you're
after a simple scalar value, as it will
require less parsing and is less likely to
break if PowerMILL changes the
formatting of the output.
string Translate(string English) Used only by plugins that support
IPowerMILLPluginTranslatable.
void AdjustPaneHeight( Call to adjust the height of a pane.
string Token,
int PaneIndex,
int Height)
string GetParameterValueTerse( Similar to GetParameterValue(), but can
string Token,
only be used for scalar values. However,
string ParameterName)
it only returns the value, unlike the non-
terse function, which will contain
formatting characters.
string LatestEvent( Call to obtain the latest event of a given
string EventName,
type. The position index indicates how
int Position)
recent the event was - 0 requests the latest
event, 1 the previous event, etc. Only a
small number of events are cached by
PowerMILL, currently 15 per type.
void AdjustTabHeight( Adjust the height of the specified tab. A
string Token,
height of less than or equal to 0 indicates
int TabIndex,
int Height) an automatic height should be used,
which is the default.
void ScaleWithDPI( Specify if a plugin's pane and tab
string Token,
windows should scale with the operating
bool Enable)
system's current dpi setting. The default
is true.
9. Creating an installer
When your plugin is complete you need an installer program to deploy it, because:
• The plugin may require a number of files to be shipped along with the main
DLL. For example, translation files.
• The installer program must check the user's machine has a suitable version of
.NET installed on it.
• The installer program must register the .NET assembly so it is created as a
COM object.
• The installer program needs to add the component category to the plugin's
registry entry, so PowerMILL discovers it at startup.
1. Right click the solution, and select Add > New Project....
2. Select the Setup Project option from Setup and Deployment > Visual
Studio Installer, ensuring you specify a sensible name and path.
1. Add the plugin's DLL to the Application Folder on the target machine by
right clicking the Application Folder, and selecting Add > Project Output....
2. On the dialog, select the plugin's project, and select Primary output.
This adds three items to the Application folder. The primary output from the
plugin project, and two dependant DLLs (the plugin framework, and the
PowerMILL's type library DLL).
3. Place the localisation folder in the Application folder by selecting Add >
Folder..., give it the name Localisation.
4. Right click it and select Add > File.... Add any translation xml files that you
wish to distribute with the plugin.
As you're distributing a plugin, not an application, you can remove the User's
Desktop and User's Programs Menu folders from the installer project.
1. Select the installer project in the solution explorer, and then open the
properties window - either by View > Properties Window or pressing
Alt+Enter.
This isn't the same as the Property pages available from the right click
menu on the project.
As you're creating a dual installer that works for both x86 and x64, don’t use the
TargetPlatform property. If you use the inbuilt installer project's Registry view to
add registry keys, the TargetPlatform setting affects whether the registry keys are
added to the normal registry hives or the 'WOW' 32-bit hives.
If an older version of the .NET framework is all that's needed (for example, version
3.5 instead of version 4.0), two steps are required:
1. Modify the prerequisites by right clicking the project in the solution explorer,
and raising the Property pages. Click the Prerequisites button, and select the
required version of the .NET framework.
2. Make the Launch condition consistent with the specified framework by right
clicking the project in the solution explorer, and selecting View > Launch
Conditions. Right click the '.NET Framework' launch condition, and set the
Version property to the correct value.
This requires some code to execute during installation and uninstallation. Visual
Studio provides a mechanism where you can add an Installer Class to the plugin
project. The installer calls this class as a Custom action.
1. Right click the plugin project in the solution explorer, and select Add > New
item.... Select the Installer Class option, and provide a suitable name.
2. Click the link to Switch to the code view. The created class derives from
System.Configuration.Install.Installer, and isn’t called in normal operation
of the assembly. However, when the assembly is installed/uninstalled, custom
installer actions cause this class to be instantiated, allowing you to override
key functions that it provides for install and uninstall actions.
The following function can be used to call regasm.exe both for x86 and x64
(assuming a 64-bit version of the .NET framework exists on the target machine), and
accepts command line options that allows you to register (/codebase) and uninstall
(/u) the assembly:
private static void RegAsm(string parameters)
{
// RuntimeEnvironment.GetRuntimeDirectory() returns something like
// C:\Windows\Microsoft.NET\Framework64\v2.0.50727\
// But we only want the "C:\Windows\Microsoft.NET" part
string net_base = Path.GetFullPath(Path.Combine(
RuntimeEnvironment.GetRuntimeDirectory(), @"..\.."));
Again, you're calling another function that you must implement. This function also
calls an external tool, reg.exe (which is a standard windows component, found in the
system directory), but this time the tool takes an option flag to separately target the 32
and 64-bit registry hives.
1. Right click the installer project in the solution explorer, and select View >
Custom Actions.
2. In the custom action list, right click Install and select Add Custom Action....
3. Navigate to the application folder, and select Primary output from ... this
ensures any installer classes that are present within the assembly are
instantiated and run at the point of installation.
If the installer fails to build, check if it's actually selected to build by:
The installer asks the user if they wish to install for Just me or Everybody. This
option is ignored, as it only affects registry keys added by the standard mechanism not
those added by means of a custom action.
You can remove an installed plugin using the normal route: Control Panel >
Programs and Features > Uninstall.
Both tools have a steep learning curve, and their use is firmly outside the scope of this
document, but there's good documentation and tutorials available for each online.
Plugins created by Delcam tend to use the NSIS installer to create a consistent user
experience, and because it lends itself better to an automated build system.
• Bold
• Italic
• Colour (with a MS Word-like popup palette)
• Mirror options (none, vertical, horizontal)
• Horizontal spacing
• Vertical spacing
The preview also has a zoom control (with a popup zoom selector, a slider, and +/-
buttons), and the ability to 'pop out' a much larger preview window as a new dialog.
The plugin supports all TrueType fonts installed on your machine, and the drop-down
list previews the actual font. These fonts are outline fonts. However, the plugin
supports engraving (or centre line) fonts. The plugin comes with one engraving font
(based on Tahoma), but you can create your own as the file format is XML (see
§10.2), and the plugin discovers any new XML files within the EngravingFonts
directory.
Attribute Description
Name The name of the font, as displayed in the drop-down menu on the
plugin.
Height The nominal height of a capital letter, in mm.
LineSpacing The vertical distance between lines of text.
Baseline The distance from the topmost position within the character's
bounding box to the baseline. Characters with 'tails' (for example,
'g') drop below the base line).
ItalicAngle If the italic option is selected, this is the skew angle applied to all
the character geometry.
Within the root element is a list of <Character> elements, one for each character that
has a definition. The <Character> element has these attributes:
Attribute Description
The text within the <Character> element defines the geometry of the character, and is
based on Microsoft's path geometry markup syntax. Essentially, this format allows
you to describe multiple line, Bézier or arc segments as a string. For a full description,
see http://msdn.microsoft.com/en-us/library/ms752293.aspx. The plugin only needs to
supports a subset of these codes.
Code Description
Defines the character 'A', with a single horizontal line, and then three connected lines
around the perimeter.
The main plugin derives from the framework base class PluginFrameworkWithPanes, in
much the same way as the example provided earlier.
The GUI for the main pane is defined in OutlineFontsPane.xaml. The main grid for
the pane is divided into 4 sections:
Most of the controls on the pane follow fairly standard WPF principals. But some
parts of the XAML file require further explanation:
• There are actually two font selection combo boxes - one for true type fonts,
and one for engraving fonts. These are displayed on top of each other, and
only one is ever visible at a time. The visibility is bound to the state of the
engraving button IsChecked property (it uses a 'visibility converter’, which is
a small class to allow you to invert the visibility state).
• The TrueType font combo box's data source is bound directly to the list of
available system fonts. However, the template used to display the content of
combo box varies depending on whether you're displaying the currently
selected item (where you don't want to preview the actual font) and the drop-
down list of items (where the preview is very helpful). The
ItemTemplateSelector attribute is set to allow this distinction between
templates.
• The colour button actually contains a vertical stack panel - the top containing
the icon, and the bottom containing a small rectangle to preview the current
colour.
• A colour 'popup' is embedded within the main pane, and its Open property is
bound to the IsChecked property of the colour toggle button. Popups are
useful as they're discrete windows but they don't steal focus from their parent.
• The 'spinners' for adjusting the font spacing are not part of the standard WPF
distribution - they're from the 'Extended WPF Toolkit', which requires two
additional assembly references (WPFToolkit and WPFToolkit.Extended).
These are available from http://wpf.codeplex.com/ and
http://wpftoolkit.codeplex.com/.
• The input text box is just a simple text box. However, the MFC (the Microsoft
GUI technology that PowerMILL is implemented with) swallows WM_CHAR
events by default, so they never get handled by managed code! The default
framework implementation of a pane includes a fix for this, by adding a
custom message hook that listens out for WM_GETDLGCODE. This is a
message issued by the offending MFC dialog code when WM_CHAR events
are handled, and returns a code that indicates that the characters shouldn't be
swallowed. If you're using the framework code, you don't need to worry about
this complication.
• The preview frame has a button next to the title text. Unfortunately, the frame
ends up going on top of the button! To work around this issue, redefine the
style for the group box. This complexity isn’t normally required.
• The preview region allows you to drag the region to scroll. It's implemented as
a ScrollViewer with hidden scroll bars, but it also captures the mouse when a
click occurs, and uses the movement to update the scroll positions.
• The grid within the ScrollViewer has its LayoutTransform set to a
ScaleTransform, with its scale bound to the zoom slider's value.
• Another popup is used to allow you to select a pre-defined scale value, and is
linked to a toggle button that displays the current zoom percentage. Pressing
any of the values on the popup will, through the 'magic' of binding, update the
toggle button, slider, and scale transform.
where the end of the command (indicated by '...') can be any of:
Command Details
• Create a pattern.
• Import the PIC file as a pattern.
• Transform the pattern to the active workplane.
• Merge the pattern.
• Set the colour.
• Delete the PIC file.
If you create a toolpath, a number of other commands are also issued to create a
toolpath from the pattern, including:
<Guid("81e14895-acc0-43e4-9747-4e29be5c6582")>
<ComVisible(True)>
<ClassInterface(ClassInterfaceType.None)>
Public Class PowerMILLPlugIn
Implements PowerMILL.IPowerMILLPlugin
Implements PowerMILL.IPowerMILLPluginPane
...
End Class
...
where SetParent() is a Win32 API function that's imported within the class.
WinForms controls are not as versatile as WPF equivalents, and are not easily
resizable. It is recommended that the MinimumPaneWidth() function within the
IPowerMILLPluginPane interface is used to ensure the pane is never smaller than the
defined size of the control. It is also recommended that the control is made at least
600 pixels wide, which is the maximum pane width, even if the full width isn't
required.
Imports System.Windows.Forms
All width and height values sent to the plugin's IPowerMILLPluginPane and
IPowerMILLPluginTab interfaces are scaled values, as if the operating system always
runs at 96dpi.
The user control used for any panes or tabs must be configured so that their
AutoScaleMode is set to Font, and the AutoScaleDimensions are set to an appropriate
value. The following class can be used to easily achieve this:
class PaneResizer
{
private float m_font_size_x;
private float m_font_size_y;
private bool m_needs_resizing;
private UserControl m_uc;
// Do the calculation
m_font_size_x = factor * orig_dpi_x * orig_size_x / dpi_x;
m_font_size_y = factor * orig_dpi_y * orig_size_y / dpi_y;
m_needs_resizing = true;
}
}
This class can then be created in the plugin's Initialise() method, and called in the
InitialisePane() and PaneResized() methods, for example:
m_pane_resizer.Resize(Width, Height);
All this class does is ensure that the user control that hosts the pane or tab fills the
area provided by the plugin mechanism; WinForms will then scale and reposition
child controls accordingly. If you use user controls, you may well have to do some of
these scaling calculations yourself.
m_plugin_services.ScaleWithDPI(m_token, false);
Using this functionality simply instructs PowerMILL not to scale the size of the
pane or tab, and thus not to provide the plugin with scaled width and height values. It
is up to the plugin developer to force the user control to not display content that is
scaled according to the system font setting.