0% found this document useful (0 votes)
206 views65 pages

Writing A PowerMILL Plugin v6

Uploaded by

Miguel Angel S
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
206 views65 pages

Writing A PowerMILL Plugin v6

Uploaded by

Miguel Angel S
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 65

Writing a PowerMILL plugin Developer's documentation

Revision No: 6

Writing a PowerMILL plugin


Type: Developer's documentation
Author: PSL
Product: PowerMILL 2013 (15.0)
Date: 14/05/2013
Revision Number: 6

Contents

1. Introduction 4

2. An overview of plugins within PowerMILL 4


2.1. Makeup of a plugin 4
2.2. Plugin GUI 4
2.2.1. Panes 4
2.2.2. Tabs 5
2.2.3. Dialogs 5
2.3. Lifecycle of a plugin instance 5
2.4. Plugin detection 6
2.5. Communication from PowerMILL to the plugin 6
2.6. Communication from the plugin to PowerMILL 7
2.7. Plugin management 7
2.8. Plugin activity log 7

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

5. Obtaining information from PowerMILL 23


5.1. Overview of obtaining information 23
5.2. Obtaining a full list of information available 24

6. Using the plugin framework DLL 24


6.1. Creating a solution that uses the framework 25

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 1 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

6.2. Base classes 26


6.3. A worked example 27
6.4. Supplying icons to PowerMILL 29
6.5. Handling panes 30
6.6. Testing the plugin 32
6.7. Handling tabs 33
6.8. Localisation 33
6.8.1. Overview 33
6.8.2. File format 33
6.8.3. Language and region rules 33
6.8.4. Generating the translation files 34
6.8.5. Using translated phrases within XAML 34
6.8.6. Using translated phrases within code 35
6.8.7. Overview of how the translation mechanism works 35
6.8.8. Using PowerMILL as a translation provider 36
6.9. Using events 36
6.9.1. Subscribing to an event 36
6.9.2. Subscribing to a filtered event 37
6.9.3. Unsubscribing from an event 37
6.10. Using utility functions 38
6.10.1. Disabling error messages 38
6.10.2. Disabling graphical updates 38
6.10.3. Command utilities 38

7. Testing and debugging 40


7.1. Creating a test host application 40
7.2. Worked example 40
7.2.1. Adding a new project to the solution 40
7.2.2. Adding the plugin pane to the host application 41
7.3. Using a surrogate to load plugins outside of PowerMILL's process space 42

8. The plugin services interface 44

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

10. A real-life example 55


10.1. Overview of functionality 55
10.2. Engraving font format 56
10.3. Overview of the Visual Studio solution 57
10.4. Command handling 58
10.5. Importing the geometry into PowerMILL 59

11. Using PowerMILL without plugins 60

12. Compatibility with other automation solutions 60

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 2 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

13. Writing plugins in VB 60


13.1. Displaying WinForm forms 60
13.2. Displaying WinForm Panes 61
13.3. Getting a WinForms TextBox to handle the 'Enter' key 62

14. Handling different system font sizes 62


14.1. Scaling panes and tabs using WPF 62
14.2. Scaling panes and tabs using WinForms 63
14.3. Forcing panes and tabs to not scale 64

15. Revision history 65

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 3 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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. An overview of plugins within PowerMILL


2.1. Makeup of a plugin
PowerMILL plugins are COM components, which are created by PowerMILL shortly
after start up, and run within the main application thread (are in-process). They are
typically .NET assemblies, packaged as DLLs, each with a single COM visible class
so that the .NET runtime creates a suitable COM Callable Wrapper (CCW), allowing
PowerMILL to create and interact with the plugin as a COM component. The class
that is COM exposed must support a specific interface, defined in PowerMILL's type
library.

2.2. Plugin GUI


The plugin mechanism support three types of GUI: panes, tabs and dialogs.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 4 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

2.3. Lifecycle of a plugin instance


The plugin mechanism manages the lifecycle of a plugin. A typical sequence of
events after starting PowerMILL is:

• PowerMILL scans the registry for registered plugins (see §2.4).


• The plugin manager creates an instance of each discovered plugin, irrespective
of its suitability or whether it's currently disabled.
• PowerMILL informs the plugin of the current locale.
• PowerMILL gets some basic details from the plugin, including: its name,
description, version, and minimum PowerMILL version. For string based
responses (such as the name and description), PowerMILL requires the plugin
to perform its own translation.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 5 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

• If the plugin isn’t suitable, or is currently disabled, PowerMILL ends the


plugin.
• If the plugin can run, it is initialised and supplied with the application's
HWND, along with a COM interface for communicating with PowerMILL
(see §2.6).

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.

2.4. Plugin detection


PowerMILL detects plugins which:

• Are a registered assembly,


• Support PowerMILL's plugin component category.

The plugin's installer program performs these actions (see §0). However, you must
perform these activities manually during the plugin development.

Microsoft's assembly registration tool (regasm.exe) performs the assembly


registration. This adds entries to the registry so that the OS can transparently load a
.NET assembly as a COM component.

To support PowerMILL's plugin component category, a registry key must be added to


the plugin's CLSID node. This allows PowerMILL to identify the COM object as a
plugin.

For more information see §3.6 and §3.7.

2.5. Communication from PowerMILL to the plugin


All plugins must support the IPowerMILLPlugin interface, which is specified in
PowerMILL's type library. PowerMILL uses this interface to communicate with each
plugin. The majority of methods/properties in the interface concern initialising the
plugin, and extracting information about the plugin such as its name, version, and
description.

There are three methods that are called to pass additional information to the plugin:

1. SerializeProjectData() - called when a PowerMILL project is loaded or


saved, giving the plugin opportunity to store data within the project
directory.
2. ProcessEvent() - called when an event that the plugin 'subscribes' to
occurs. Event data is expressed in snippets of XML. See §4.
3. ProcessCommand() - called when a command relating to the specific plugin
is issued.

Plugins commands are of the form:

PLUGIN {PLUGIN-GUID} COMMAND STRING

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 6 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

Where:

{PLUGIN-GUID} is the plugin's GUID


COMMAND STRING is whatever the plugin wants it to be. The plugin is only
supplied with this last section of the overall command, and is responsible for
parsing it. This mechanism allows you to drive plugins by commands.

2.6. Communication from the plugin to PowerMILL


When you initialise a plugin, it is given a pointer to an interface that it uses to
communicate with PowerMILL. This interface is called IPluginServices. The plugin
is not given a pointer to PowerMILL's main application interface, although many of
the commonly used functions within this interface are duplicated in the plugin
services interface.

The plugin services interface allows the plugin to:

• Issue PowerMILL commands.


• Manage the progress break.
• Add messages to the plugin log.
• Subscribe to specific events (see §4).
• Obtain the Project interface.
• Request named information from PowerMILL (see §5).
• Check if a PAF is valid.
• Access parameters without issuing commands.

A plugin can get a wealth of information about any PowerMILL entity using the
Project interface. For more information see §0.

2.7. Plugin management


When a plugin is installed on a machine, it is enabled by default. The user can enable
or disable any plugin they wish using the PowerMILL plugin manager.

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.

2.8. Plugin activity log


The plugin manager also provides access to a plugin log. All activities relating to
plugins are logged, including:

• 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

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 7 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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:

• Microsoft Visual Studio 2010 Professional


• C#
• .NET Framework 4.0
• Windows Presentation Foundation (WPF) for the GUI.

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.

3.2. Project setup


To setup a project:

1. Create a new project from File > New > Project....


2. Choose Visual C# > Windows > Class Library, giving it the name
BasicPlugin and specifying a suitable location.

This creates a solution with a single assembly with a DLL output.

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:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 8 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

3. The automatically generated class has the name Class1. Rename it to


BasicExamplePlugin.

3.3. Adding required references


The assembly needs a reference to PowerMILL's type library, so it can obtain all the
necessary details about the plugin and plugin services interfaces. A small amount of
work is required to extract the type library from PowerMILL's executable, analyse the
metadata it contains, and package it as a DLL to use within .NET code.

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.

1. Using a standard windows command window, move to the PowerMILL's


executable directory. Add the Microsoft SDK tools to the PATH variable if it's
not already present:

SET PATH=%PATH%;C:\Program Files (x86)\


Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\x64

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 :

C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\x64

2. Generate a strong name key file using the strong name tool sn.exe:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 9 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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

tlbimp.exe may issue a warning about importing a type library into


platform agnostic assembly; this warning can be safely ignored.

If you're using .NET 3.5, be


b careful not to use a version of tlbimp.exe
from a newer version of the SDK (for example, .NET 4.0) as it will result in a
version of PowerMILL.dll that can't be used by .NET 3.5.

4. Move this DLL to the plugin project's 'bin' directory.


directory

he new DLL in the project, in the Solution Explorer


5. To reference the xplorer, from the
References context menu, select Add Reference....

6. Select PowerMILL.dll.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 10 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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

3.4. Implementing PowerMILL's plugin interface


After you have established a reference to PowerMILL's type library, derive the main
class for the DLL from the plugin interface, and implement it.

1. Derive the class from PowerMILL.IPowerMILLPlugin.


2. Right click the interface name and select the option to implement it.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 11 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 12 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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
)

For this example, use the following code:


// Private data members
private string m_token;
private PowerMILL.PluginServices m_services;
private int m_parent_window;

// IPowerMILLPlugin Interface implementation


public string Author { get { return "Delcam PLC"; } }
public string Description { get { return "An example plugin."; } }
public void DisplayOptions() { MessageBox.Show("There are no options!"); }
public bool HasOptions { get { return true; } }
public void Initialise(string Token, PowerMILL.PluginServices pServices,
int ParentWindow)
{
m_token = Token;
m_services = pServices;
m_parent_window = ParentWindow;
}
public void MinPowerMILLVersion(out int pMajor, out int pMinor,
out int pIssue)
{
pMajor = 13;
pMinor = 1;
pIssue = 0;
}
public string Name { get { return "Basic Plugin"; } }
public void PluginIconBitmap(out PowerMILL.PluginBitmapFormat pFormat,
out byte[] pPixelData,
out int pWidth, out int pHeight)
{
pFormat = PowerMILL.PluginBitmapFormat.PMILL_BITMAP_FORMAT_FAIL;
pPixelData = null;
pWidth = 0;
pHeight = 0;
}
public void PreInitialise(string locale) { }
public void ProcessCommand(string Commmand) { }
public void ProcessEvent(string EventData) { }
public void SerializeProjectData(string Path, bool Saving) { }
public void Uninitialise()
{
m_services = null;
GC.Collect();
}

public void Version(out int pMajor, out int pMinor, out int pIssue)

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 13 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

{
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.

3.5. Making the class accessible through COM


To create a .NET class as a COM object, you must make it made ComVisible so that
a COM Callable Wrapper (CCW) is created for the class. This wrapper is a
'traditional' COM object which:

• 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.

1. Add another namespace statement to make the class ComVisible:


using System.Runtime.InteropServices; // For interop attributes, including:
// Guid, ComVisible, ...

2. Add these attributes immediately before the class definition:


[Guid("6e90c6bf-ebe9-427f-bffb-e883815ea72e")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]

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.

3.6. Registering the assembly


You must register all .NET assemblies that are to be loaded as COM components
using an appropriate version of regasm (there are 32 and 64-bit versions) from within
the DLL directory, with both the /register and /codebase options. This adds
appropriate keys to the registry that allow the OS to find the assemblies given a
GUID.

For example, to register a plugin for use on an x86 platform:

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 14 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

For x64 plugins, replace Framework with Framework64. If the assembly is


compiled as Any Platform,
Platform it can be registered for both x86 and x64 simultaneously.
simultaneously

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.

3.7. Supporting PowerMILL's plugin component category


To support
upport PowerMILL's plugin component category,
category you need to add a registry key
to the plugin’s CLSID, which acts as a flag, indicating that the registered assembly is
a PowerMILL plugin. PowerMILL's component category (a GUID) is:

{311b0135-1826-4a8c
4a8c-98de-f313289f815e}

A command line tool addss this registry key:

REG ADD "HKCR\CLSID\{6e90c6bf


6e90c6bf-ebe9-427f-bffb-e883815ea72e}\
Implemented Categories\
\{311b0135-1826-4a8c-98de-f313289f815e}"
" /reg:32 /f

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.

3.8. Trying it out


At this stage,, when PowerMILL runs, it finds the plugin and automatically enables
enable it.
It appears in the Plugin Manager dialog. Selecting the Options button
tton displays the
message There are no options!.
options!

PowerMILL uses a default icon as you didn't return an icon in response to


PluginIconBitmap(...).

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 15 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

3.9. Supporting a dialog


This basic plugin currently doesn't have any GUI (other than a message box). Plugins
don't need to have any GUI. For instance, a plugin that writes a log file based on the
events it received from PowerMILL doesn’t need any GUI. However, the majority of
plugins will contain some form of GUI - panes or dialogs. Panes are embedded
directly within PowerMILL's main window, and are managed by the application.
However, supporting panes does require the plugin to implement a second interface.
For this basic example you'll restrict the GUI to a simple dialog, raised by a
command, which in turn can issue a command. For more information on supporting
panes see §6.5.

This example uses WPF technology.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 16 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

1. Add two new namespaces to the plugin class:


using System.Windows; // For WPF windows
using System.Windows.Interop; // For WindowInteropHelper

2. Add a member variable to the dialog:


private BasicForm m_basic_form = null;

3. Add a method to raise the dialog.

Use m_parent_window, which was cached in Initialise(), to correctly


parent the dialog:
private void RaiseForm()
{
// Create the new window, and ensure it's not displayed in the taskbar
m_basic_form = new BasicForm();
m_basic_form.ShowInTaskbar = false;

// Attempt to parent the window with PowerMILL


WindowInteropHelper wih = new WindowInteropHelper(m_basic_form);
wih.Owner = new IntPtr(m_parent_window);

// Show the new window


m_basic_form.Show();
}

This gives the plugin the ability to raise a dialog, but the method isn't called.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 17 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

3.10. Handling commands


This shows you how to implement a command, issued in PowerMILL, to raise the
plugin's dialog. You can issue the command by:

• Typing it on the command line.


• Adding it to a button on a custom toolbar.
• Adding it to their user menu (raised by right clicking in the unused space in
the explorer tree control).

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:

PLUGIN {6e90c6bf-ebe9-427f-bffb-e883815ea72e} RAISE BASIC_FORM

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

Modify the method to:


public void ProcessCommand(string Command)
{
if (Regex.IsMatch(Command, @"\s*raise\s*basic_form\s*",
RegexOptions.IgnoreCase))
{
RaiseForm();
}
}

In a regular expression, \s means any whitespace character, and * means it can


repeat 0 or more times. It also uses the IgnoreCase. So. RAISE BASIC_FORM is the same
as raise basic_form which is trhe same as ' Raise Basic_Form '.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 18 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

3.11. Issuing commands


Most plugins communicate with PowerMILL
P through commands. This shows you
how to add a control to the dialog, and make the dialog issue a command.

Add this element as a child to the window's


win main grid element:
<Button Width="150" Height="30"
Height
Click="Button_Click">
="Button_Click">
Raise Block form
</Button>

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 BasicForm class lacks two pieces of information to issue a PowerMILL


command:

• 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

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 19 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

The modifications required to supply these members when constructing the object are:

1. Add two private member variables to the BasicForm class:


private string m_token;
private PowerMILL.PluginServices m_services;

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():

m_basic_form = new BasicForm(m_token, m_services);

To populate Button_Click(...) with the necessary code to issue a PowerMILL


command:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (m_services != null)
{
m_services.DoCommand(m_token, "FORM BLOCK");
}
}

The result after rebuilding the plugin and running PowerMILL:

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:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 20 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

• Add panes within PowerMILL's main window.


• Translate all phrases that appear on the GUI to support multiple locales.
• Respond to events that occur within PowerMILL.
• Interrogate PowerMILL for additional information.

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:

<EntityActivated event_group="EntityManager" subscription_id="17"


unique_id="231">
<EntityType>Toolpath</EntityType>
<Name>TP2</Name>
<Deactivated>TP1</Deactivated>
</EntityActivated>

The event_group attribute helps to 'triage' incoming events, and pass them to the most
appropriate handling function within the plugin.

The subscription_id attribute corresponds to the ID returned when you subscribe to


the event, helping disambiguate the event if two or more different parts of the plugin
has registered for the same event. The child elements contain further information
about the event. In this case, the event reports the type of entity activated, the name of
the activated entity, and the name of the entity that was deactivated (which could be
blank).

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 21 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

Event Group Examples


Application ProjectOpened, ProjectSaved,
ProcessedCommand
EntityManager EntityActivated, EntityCreated,
EntityDeleted, EntityRenamed
Entity specific events: NCProgram, NCProgramItemAdded,
Toolpath, MachineTool, ... ToolpathCalculationStarted,
MachineToolMovement
Parameter EntityParameterChanged,
CurrentParameterChanged,
SystemParameterChanged
Simulation SimulatePlay, SimulateSearch,
SimulateActionComplete.

4.2. Subscription to events


When you initialise a plugin, it is given:

• A plugin services interface it can use to communicate with PowerMILL.


• A token to identify itself.

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.

4.3. Filtering events


Often, events are too 'generic', meaning the plugin is notified about events that are of
no interest. A second subscription function allows the plugin to specify a filter field
and a filter value.

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:

int subscription_id = m_services.SubscribeToFilteredEvent(


m_token,
"EntityActivation",
"EntityType",
"Toolpath"
);

4.4. Event notifications


When an event occurs that has a corresponding subscription, PowerMILL will notify
the plugin by calling the interface function:
void ProcessEvent(string EventData);

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 22 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

where EventData is the XML snippet.

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.

4.5. Unsubscribing from events


A plugin can unsubscribe to an event that it previously subscribed to when it no
longer wishes to hear about it. Since a plugin can subscribe to the same event multiple
times, and some of these may be filtered, it's not enough to unsubscribe using the
name of the event. You must use the subscription ID that was returned when
subscribing:
m_services.UnsubscribeFromEvent(m_token, subcription_id);

4.6. Obtaining a list of events


You can obtain an XML document (as a string), which details all of the events that a
specific version of PowerMILL supports, and the additional information each event
carries. This property on the services interface is:
string event_list = m_services.EventList;

4.7. Finding out about recent events


It's not necessary to subscribe to events - a plugin can request to hear about the last
value of a specific event, or indeed the last few values:
string event_list = m_services.LatestEvent("EntityActivated", 0);

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.

5. Obtaining information from PowerMILL


5.1. Overview of obtaining information
Occasionally, a plugin may need to obtain information from PowerMILL that isn't
available using the project interface or from inspecting the result of a call to
DoCommandEx(). PowerMILL provides a function that accepts the name of a piece of
information (as a string), and returns the information (also as a string). For instance,
to determine the current unit system:
string info = m_services.RequestInformation("Units");
if (info.ToLower() == "mm") {
// ...
} else {
// ...
}

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 23 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

If the named information doesn’t exist, PowerMILL returns an HRESULT of E_FAIL,


which results in a CLR exception that needs to be caught with a try - catch block.

5.2. Obtaining a full list of information available


You can get a full list of information that's available using:
string info_list = m_services.InformationList;

The current list is:

Named information Return valye


"Units" "mm" or "Inches".
"ErrorsDisplayed" true or false depending on whether errors are
displayed.
"MessagesDisplayed" true or false depending on whether messages are
displayed.
"EditingMode" Returns the name of any editing mode that
PowerMILL is in. For example, it could return
"Curve Editor". If PowerMILL is not in a mode,
it returns an empty string.
"Folders" Returns a list of all folders within the explorer.
"ShadingMinToolRadius" Returns the current minimum tool radius used for
shading.
"ShadingUndercutDraftAngle" Returns the current undercut draft angle used for
shading.
"Codebase" Returns the PowerMILL codebase number.
"SupportsTranslation" true or false depending on whether PowerMILL
supports translation. This is only applicable for
plugins developed by Delcam, because
PowerMILL's translation database won't be aware
of strings from 3rd parties.
"SupportsTabs" true or false depending on whether PowerMILL
supports tabs. Tabs are only available from
PM2013 onwards.
"SupportsEventCaching" true or false depending on whether PowerMILL
supports event caching. This is only available
from PM2013 onwards.

6. Using the plugin framework DLL


The steps involved to create a basic plugin are outlined in §3. There is a plugin
framework available for building plugins, which reduces the amount of 'boilerplate'
code required to produce a robust and professional plugin. This framework provides
base classes that implement the required interfaces, and tools to manage events,
panes, translation, and other common tasks.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 24 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

This section shows an example of building a plugin using the framework.

6.1. Creating a solution that uses the framework


The initial steps are identical to those for the basic plugin:

1. Setup the project (see §3.1 - §3.3), but name it PluginUsingFramework.


2. Make the class COM visible (see §3.5), ensuring a unique GUID is generated.
3. Register the COM objects (see §3.6 - §3.7).

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....

2. Locate the framework project (PluginFramework.csproj) and click Open.


3. You need to assign a public/private key pair to the project so that it can have a
strong name. Right click the framework project, and select Properties.
4. Navigate to the Signing page, and select the Sign the assembly option.
5. Specify a key file. For more information on how to do this, see §3.3.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 25 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

8. Ensure the framework project is built.

6.2. Base classes


The framework offers four different base classes:

• PluginFrameworkWithoutPanes - has no support for panes.


• PluginFrameworkWithPanes - has support for panes.
• PluginFrameworkWithPanesAndTabs - has support for panes and tabs.
• PluginFrameworkNoPowerMILL - used when building a test harness for the
plugin. For more information see §7.

All of these classes support a common interface: IPluginCommunicationsInterface.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 26 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

The IPluginCommunicationsInterface also provides a number of convenience


methods. They largely map to equivalent methods on the PluginServices interface,
but they add a certain amount of value. They remove the need for calls to obtain the
interface and communications token, and also perform parsing of returned data. The
base classes provide implementations for these properties and methods.

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).

6.3. A worked example


Continuing the example from §6.1, to create a plugin that supports a pane, derive the
class from PluginFrameworkWithPanes. A couple of using statements simplifies the
code:
using Delcam.Plugins.Framework;
using Delcam.Plugins.Localisation;

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 27 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

These methods and properties have placeholder implementations, and need to be


implemented:

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.

Replace the placeholder implementations with the following code:


protected override void register_panes()
{
// Still to do
}
public override string PluginName
{
get { return TranslationUtils.Translate("An Example Plugin"); }
}
public override string PluginAuthor { get { return "Delcam PLC"; } }
public override string PluginDescription
{
get
{
return TranslationUtils.Translate(
"An example plugin demonstating use of the framework"
);
}
}
public override string PluginIconPath
{
get { return "Images/plugin_icon.png"; }
}
public override Version PluginVersion { get { return new Version(1, 0, 0); } }

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 28 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

public override Version PowerMILLVersion


{
get { return new Version(13, 1, 4); }
}
public override bool PluginHasOptions { get { return false; } }
public override string PluginAssemblyName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
}
}
public override Guid PluginGuid
{
get { return new Guid("7b77679b-74c1-4eb8-86bf-205beb01a3a4"); }
}

A few notes on this code:

• register_panes() is still a placeholder.


• PluginName and PluginDescription make use of the TranslationManager class
(see §6.7).
• PluginIconPath specifies the path to the icon, which you must include as a
resource - see §6.4.
• PluginAssemblyName uses reflection to obtain the assembly name, so it needn't
be 'hard coded'.

6.4. Supplying icons to PowerMILL


PowerMILL displays an icon on the plugin manager dialog, which the plugin can
supply if it wishes to replace the default icon. PowerMILL calls the following method
on the IPowerMILLPlugin interface to obtain the pixel data:
public void PluginIconBitmap(
out PowerMILL.PluginBitmapFormat pFormat,
out byte[] pPixelData,
out int pWidth,
out int pHeight
)

You need to set the following out parameters:

• 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:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 29 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

Icons are also required for plugin panes.


panes For more information see §6.5
6.5.

6.5. Handling panes


To support panes, the plugin must implement the IPowerMILLPluginPane interface. The
PluginFrameworkWithPanes framework base class does this, but this class is abstract
because register_panes() must be implemented.

You need to create a pane,, and register it with the base class:

righ clickingg on the project and selecting Add


1. Create a WPF User Control by right
> New item....

2. Select Visual C# templates > WPF > User Control (WPF) and name it
ExamplePluginPane
ExamplePluginPane.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 30 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

3. Modify the XAML to correspond with the example:


<UserControl
x:Class="PluginUsingFramework.ExamplePluginPane"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:Delcam.Plugins.Localisation;assembly=PluginFramework">
<StackPanel Background="White" MinWidth="200" MinHeight="200">
<TextBlock Margin="10" Text="This is an example pane."/>
</StackPanel>
</UserControl>

Some notes on dissecting this XAML:

• The x:Class attribute defines the namespace of the corresponding class.


• The first two xmlns definitions are common to all XAML files.
• The third namespace definition defines an alias t to refer to the localisation
class defined within the framework. See §6.7.
• A UserControl can have only one child element. In this case, it's a stack panel,
with a white background colour, and a minimum size of 200 x 200.
• Add a text block to the stack, as a placeholder until you develop 'real' content.

This text isn't translated.

Returning to the register_panes() function, replace it with:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 31 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

private ExamplePluginPane m_pane;

protected override void register_panes()


{
m_pane = new ExamplePluginPane();
ExamplePluginPane
register_pane(
new PaneDefinition(
m_pane,
200,
200,
TranslationUtils.Translate(
.Translate("Example pane"),
"Images/pane_icon.png"
)
);
}

This function:

1. Creates the pane, wraps


wrap it in a PaneDefinition object, and then calls
call a
framework registration function.
2. Specifies the height and minimum
min width for the pane.
3. Specifies the translated title of the pane.
icon Pane icons are 24 x 24, but otherwise are
4. Specifies the path to the pane's icon.
handled in exactly the same way as §6.4.
§

The title and icon are displayed


layed on the pane's header.

6.6. Testing the plugin


You can now build the plugin.
plugin Ensure it's registered correctly (see §3.6
3.6 - §3.7).

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 32 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

6.7. Handling tabs


To support tabs, the plugin must implement the IPowerMILLPluginTab interface. The
PluginFrameworkWithPanesAndTabs framework base class does this, but this class is
abstract because register_panes() and register_tabs() must be implemented.

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.

6.8.2. File format


Translation files exist within a Localisation sub-directory alongside the plugin's DLL,
and are simple XML files. Each file has a naming scheme that has two parts - the
language and the region - each denoted by two characters. For example, 'en-GB.xml'
dictates that the language is English, and the region is Great Britain. This format
follows ISO639/3166. This is an example of a translation file with a single translation:

<?xml version="1.0" encoding="utf-8"?>


<languagedocument language="en-GB">
<message>
<english>Preview</english>
<translation>Preview</translation>
</message>
</languagedocument>

For each phrase that needs translation, a <message> element exists, containing one
sub-child with the <english> and another with the <translation>.

6.8.3. Language and region rules


If PowerMILL reports that a certain locale (a language and a region) is in use, the
translation mechanism looks for an exact match. If this fails, it looks for a match for
any file that supports the same language, irrespective of the region. If there is no
match it uses English.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 33 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

6.8.4. Generating the translation files


You can generate the translation files manually, but we don’t recommend it as it
quickly becomes impractical as the number of phrases needing translation increases.
The framework provides a method to automatically generate a file containing all
phrases that have been requested that couldn't be translated. You can temporarily
override the shutdown_framework() function to call this function to generate this file:
public override void shutdown_framework()
{
// Temp - output untranslated phrases
XMLTranslationProvider.OutputUntranslatedPhrases(PluginInstallPath, "en-GB");

// Call the base class method


base.shutdown_framework();
}

The en-GB locale generates a en-GB.xml.untranslated file. If no en-GB.xml exists


you can rename this file to form the 'real' version. However, if en-GB.xml already
exists and contains some, but not all, of the required translations, then the lines within
the untranslated version are manually appended to the real version.

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:

• Brevity with the XAML file or source code.


• Inability for XAML file to contain certain characters within an attribute
markup; a common example is the ' character.
• Disambiguation when one phrase can have two meanings; for example
'Machine' in English can be a verb (to machine a part) or a noun (a machine
tool).

6.8.5. Using translated phrases within XAML


To allow translation of a phrase within a XAML file, you must:

1. Add a namespace to the root element of the XAML file:


xmlns:t="clr-namespace:Delcam.Plugins.Localisation;assembly=PluginFramework

This instructs the XAML interpreter where to locate the translation markup
extension.

2. Use the markup extension for any text needing translation:


<TextBlock Margin="10" Text="{t:Translate This is an example pane.}"/>

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 34 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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;

public ExamplePluginPane(IPluginCommunicationsInterface comms)


{
m_comms = comms;
InitializeComponent();
}

#region ITranslationSupporter Members


public TranslationManager Translator
{
get { return m_comms.TranslationUtils; }
}
#endregion
}
}

4. Supply an object that supports the IPluginCommunicationsInterface interface


when you create the pane. The framework base class does this:
protected override void register_panes()
{
m_pane = new ExamplePluginPane(this);
...
}

6.8.6. Using translated phrases within code


Obtaining a translated phrase within source code is straightforward. Include the same
namespace, and then use the framework translation utility to return the translated
string:
string translated = TranslationUtils.Translate("Some text");

6.8.7. Overview of how the translation mechanism works


The translation mechanism is based on Christian Mosers, details of which can be
found at:

http://www.wpftutorial.net/LocalizeMarkupExtension.html

However, there are two significant differences to the scheme he presents:

• 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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 35 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

• 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.

6.8.8. Using PowerMILL as a translation provider


It is possible to use PowerMILL as a translation provider by implementing the
IPowerMILLPluginTranslatable interface; however, this is only appropriate for
plugins developed by Delcam, because it relies on the strings being build into
PowerMILL's translation database. Plugins developed by third parties will not be able
to add strings to this database, so should avoid this interface.

6.9. Using events


The framework base classes contain an EventManager. This class is capable of:

• 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.

6.9.1. Subscribing to an event


Firstly, you must modify the XAML to add a text box, so you can display content in
response to events. At the bottom of the stack panel add:
<TextBlock x:Name="PaneText" Margin="10 0 10 10"/>

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>();

void ListenActivation(string event_name, Dictionary<string, string>


event_arguments)
{
string entity_type = event_arguments["EntityType"];
string entity_name = event_arguments["Name"];
string result = entity_type + ": " + entity_name;
if (!m_active_entities.Contains(result))
{
m_active_entities.Add(result);

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 36 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

}
UpdateEntityList();
}

void ListenDeactivation(string event_name, Dictionary<string, string>


event_arguments)
{
string entity_type = event_arguments["EntityType"];
string entity_name = event_arguments["Name"];
string result = entity_type + ": " + entity_name;
if (m_active_entities.Contains(result))
{
m_active_entities.Remove(result);
}
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();
}

This code will maintain a simple list of the active entities:

• 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.

6.9.2. Subscribing to a filtered event


It's possible to subscribe to a filtered event, reducing the number of unwanted events.
For example, to only be notified about toolpath activations:
m_comms.EventUtils.Subscribe(
new EventSubscription(
"EntityActivated",
"EntityType",
"Toolpath",
ListenActivation
)
);

6.9.3. Unsubscribing from an event


If an event subscription is only temporary, a reference to the subscription should be
cached, and used to unsubscribe:
EventSubscription sub = new EventSubscription("EntityActivated", Listen);
m_comms.EventUtils.Subscribe(sub);
...
m_comms.EventUtils.Unsubscribe(sub);

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 37 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

6.10. Using utility functions


The framework contains certain utility functions that simplify certain tasks.

6.10.1. Disabling error messages


PowerMILL can send error and warning messages to the command window instead of
raising a modal dialog and halting program execution. For automated extensions to
PowerMILL this is very useful, but for normal user interaction is inadvisable. The
'ideal' solution is for the plugin to issue a series of commands, which:

• Check if errors/warning are reported normally.


• Disable them, if necessary, by issuing the relevant commands.
• Issue the commands required by the plugin to do whatever it needs to do.
• Reinstate the error/warning messages if the plugin disabled them.

The framework provides a tool to do this:


using (DisableDialogMessages ddm = new DisableDialogMessages(comms))
{
...
}

where comms is a reference to the IPluginCommunicationsInterface interface.

DisableDialogMessages uses the standard .NET IDisposable interface convention


to reinstate the messages as code execution leaves the using block. By default,
DisableDialogMessages disables both error and warning messages. An alternative
constructor exists that allows you to disable either one.

6.10.2. Disabling graphical updates


Graphical updates can also be suppressed:
using (DisableGraphics dg = new DisableGraphics(comms))
{
...
}

This prevents 'normal' view refreshes. It doesn’t suppress 'transitory' graphics


(drawn using XOR).

6.10.3. Command utilities


You can drive plugins by commands (see §3.10). It is the plugin's responsibility to
parse the commands given to it, but the framework provides a number of utility
methods to facilitate this.

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:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 38 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

PLUGIN {Guid} DIAMETER 5.5

Is passed to the plugin as a string 'DIAMETER 5.5'. This is broken down into two
tokens 'DIAMETER' and '5.5'.

Such a command could be parsed using the following code:


while (tokens.Count > 0)
{
// Get the next token, and remove from list
string token = CommandUtils.GetStringToken(ref tokens);

// Search for known keywords


switch (token)
{
case "DIAMETER":
double? diam = CommandUtils.GetRealToken(ref tokens);
if (diam != null)
{
SetTheDiameter((double)diam);
}
//...other tokens...
default:
break;
}

// Loop until we're done, to allow for chained commands


}

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 39 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

7. Testing and debugging


7.1. Creating a test host application
When developing a plugin, it's a good idea to test it in isolation from PowerMILL
where possible because:

• 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.

7.2. Worked example


This example is a continuation of the example in §6, and displays the pane within a
'host' application.

7.2.1. Adding a new project to the solution


It's best to add the 'host' application as a new project to the plugin's Visual Studio
solution. Right click the solution, select Add > New Project, and select WPF
Application. Give it a sensible name and path.

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

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 40 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

At the moment this displays a blank window.

7.2.2. Adding the plugin pane to the host application


To add the plugin pane to the host application's window, you must include a reference
to the plugin.

1. Include a reference to the plugin by right clicking on References under the


host project, and select Add Reference.... Select the plugin project from the
Projects tab, and accept the dialog.
2. Add the framework as a reference. Within MainWindow.xaml.cs, add
suitable using statements for the framework and plugin:
using Delcam.Plugins.Framework;
using PluginUsingFramework;

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();

Guid guid = new Guid("7b77679b-74c1-4eb8-86bf-205beb01a3a4");


PluginFrameworkNoPowerMILL plugin_framework = new
PluginFrameworkNoPowerMILL(guid, "PluginUsingFramework");
Content = new ExamplePluginPane(plugin_framework);
}

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:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 41 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

The plugin pane, in its constructor, is attempting to subscribe to a PowerMILL event,


which requires the plugin to be connected to PowerMILL. There are two solutions to
this issue:

1. Modify the pane's constructor to check with the


IPluginCommunicationsInterface if the plugin is actually connected to
PowerMILL, and act accordingly.
2. Create a derived instance of PluginFrameworkNoPowerMILL that simulates
receiving event requests. This is useful for creating an automated test harness
for a plugin, but is beyond the scope of this document.

Continuing with option 1, modify the relevant lines of the constructor:


if (m_comms.ConnectedToPowerMILL)
{
m_comms.EventUtils.Subscribe(new EventSubscription("EntityActivated",
ListenActivation));
m_comms.EventUtils.Subscribe(new EventSubscription("EntityDeactivated",
ListenDeactivation));
}

Running the host application now result in:

7.3. Using a surrogate to load plugins outside of PowerMILL's process


space
Plugins normally run within the process space of the PowerMILL executable, which
means they can't be debugged. It is possible to create a surrogate process, which runs
outside of PowerMILL's process space, and can host the plugins in a very similar
manner to how PowerMILL hosts them normally. PowerMILL will communicate
with this external process using inter-process COM marshalling, which will be slower
than in-process communication.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 42 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

Use of the surrogate is not supported by the PowerMILL development team


- it is purely a debugging aid. Future features and API changes made to the
plugin mechanism are likely to be incompatible with the surrogate, and could
cause PowerMILL to crash. Newer versions of the surrogate may be issued to
address any such problems, but no guarantees are provided.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 43 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

8. The plugin services interface


Every plugin gets the IPluginServices interface when they are initialised, and is the
only means a plugin can communicate with PowerMILL. The complete list of the
methods/properties is given in the table. Many of the functions require a Token. This
is the token given to the plugin when it is initialised so PowerMILL can identify
which plugin is communicating with it.

Methods/Properties Description

int DoCommand(string Token, Called to issue a normal PowerMILL


string Command)
command, which fails if PowerMILL is
int DoCommandEx(string Token,
string Command, busy. The function doesn’t return until
out object pResponse) the command is processed or failure
occurs. The Ex version returns the output
from the command window, and can be
safely cast to a string.
Busy means PowerMILL is
processing a command.
int InsertCommand(string Token, Called to issue a command by adding it to
string Command)
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).
void AppendPluginLog(string Token, Appends a message to the plugin log,
string Message)
which is available on the plugin manager
dialog.
void StartProgressBreak(string Token, Allow a plugin to raise, increment and
string Message)
lower PowerMILL's progress bar. You
bool UpdateProgressBreak(string Token,
int PercentComplete) can supply a message when raising the
void EndProgressBreak(string Token) progress bar, and then you can issue %
updates. If the user presses the break
button at any point, the return value from
UpdateProgressBreak() is true and the
plugin should break from its calculation.
int SubscribeToEvent(string Token, Allows the plugin to subscribe to events;
string EventName)
see §4. Note the property to obtain a full
int SubscribeToFilteredEvent(
string Token, string EventName, list of supported events.
string FilterField,
string FilterValue)
void UnsubscribeFromEvent(
string Token, int SubscriptionID)
string EventList
string RequestInformation( Allow the plugin to request information.
string InfoName)
For more information see §5. Note the
string InformationList
property to obtain a full list of supported
information.
Project Project Returns the Project interface, allowing
access to all of PowerMILL's entities and

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 44 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 45 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

9.1. Visual Studio


Creating an installer with Visual Studio is straightforward. However, deployment
options are not available with Express editions. See §9.2 for other options.

9.1.1. Adding an installer project


The easiest way to create an installer is to add an installer project to the plugin's
solution. This has the added benefit that whenever you rebuild the solution, the
installer will always be up to date, dramatically reducing the effort required to
produce an installer each time you make changes to the plugin.

To add an installer to the solution:

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 46 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

9.1.2. Setting up the files to be installed


The installer project has several 'views'. You need the File System view which
automatically appear when you add the project.

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.

9.1.3. Configuring the installer properties


You must modify a few properties, such as the installer's name and the version. These
properties affect text that appears when the installer program runs, or within Control
Panel > Programs and Features.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 47 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

2. Ensure that sensible values are provided for at least:


• Manufacturer - used in install path, and visible in Programs & Features.
• Product Name - used in install path, and visible in Programs & Features.
• Version.

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.

9.1.4. Prerequisites and launch conditions


One of the purposes of having the installer is to ensure that the .NET framework
installed on the user's machine is new enough to run the plugin. By default, the
installer will add a 'Prerequisite' dependence on the latest version of the .NET
framework installed on the machine.

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:

• Modifying the prerequisites.


• Updating the launch conditions.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 48 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

9.1.5. Ensuring assembly is registration


After installing the plugin's DLL on the user's machine, you must register it for COM
interop. This involves adding a few registry entries so Windows can associate the
COM component's CLSID (a GUID) with the interface name and plugin's DLL.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 49 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

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.

3. Use Intellisense to override and provide skeleton implementations of


Install(...) and Uninstall(...) (by typing override Inst... and override
Unin...).

4. Add a single line of code to each:


public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);

// Register the .NET assembly


RegAsm("/codebase");
}

public override void Uninstall(IDictionary savedState)


{
base.Uninstall(savedState);

// Unregister the .NET assembly


RegAsm("/u");
}

where RegAsm(...) is a function that to register the assembly.

There are two common ways to register an assembly:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 50 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

1. Using a command line tool, regasm.exe, which is part of the .NET


Framework.
2. Calling System.Runtime.InteropServices.RegistrationServices.
RegisterAssembly(). This only registers the component using the current
platform. It ignores the target platform property set within the installer, and
uses the platform of the install program itself, which is always x86.

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(), @"..\.."));

// Create paths to 32bit and 64bit versions of regasm.exe


string[] to_exec = new[]
{
string.Concat(net_base, "\\Framework\\",
RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe"),
string.Concat(net_base, "\\Framework64\\",
RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe")
};

// Get the path to the plugin's location


var dll_path = Assembly.GetExecutingAssembly().Location;

foreach (string path in to_exec)


{
// Skip the executables that do not exist; This most likely happens on
// a 32bit machine when processing the path to 64bit regasm
if (!File.Exists(path)) continue;

// Build the argument string


string args = string.Format("\"{0}\" {1}", dll_path, parameters);

// Launch the regasm.exe process


LaunchProcess(path, args);
}
}

These using statements are required for this code:


using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;

Also, LaunchProcess(...) needs a simple implementation:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 51 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

private static void LaunchProcess(string path, string arguments)


{
// Create a new process object, and setup its startup structure
Process process = new Process
{
StartInfo =
{
CreateNoWindow = true,
ErrorDialog = false,
UseShellExecute = false,
FileName = path,
Arguments = arguments
}
};

// Start the process, and wait for it to terminate


using (process)
{
process.Start();
process.WaitForExit();
}
}

9.1.6. Ensuring the component category is added


You must ensure the plugin implements PowerMILL's component category. Add this
after registering the assembly:
// Register the plugin component category
RegisterCOMCategory(true);

and this before unregistering it:


// Unregister the plugin component category
RegisterCOMCategory(false);

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 52 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

static string plugin_guid = "{7b77679b-74c1-4eb8-86bf-205beb01a3a4}";

static string plugin_comp_category = "{311B0135-1826-4A8C-98DE-F313289F815E}";

private static void RegisterCOMCategory(bool register)


{
// Build the registry key we wish to add
string reg_key = "HKCR\\CLSID\\" + plugin_guid +
"\\Implemented Categories\\" + plugin_comp_category;

// Build the path


string path = Environment.SystemDirectory + "\\reg.exe";

// Loop over the platforms we wish to add the key to


string[] platforms = new[] {"32", "64"};
foreach (string platform in platforms)
{
// Build the arguments
string args = (register) ? "ADD" : "DELETE";
args += " \"" + reg_key + "\" /reg:" + platform + " /f";

// Launch the reg.exe process


LaunchProcess(path, args);
}
}

9.1.7. Adding custom actions


In order for the installer to run the code contained within the Installer class, you must
add a Custom Action.

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.

4. Repeat the process for the Unisnstall custom action.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 53 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

9.1.8. Building and testing the installer


It is possible to build the solution and test the resulting MSI (Microsoft Installer)
package. Remove the manually-added component category registry keys and
unregister the development version of the DLL before testing the installer.

If the installer fails to build, check if it's actually selected to build by:

1. Right clicking the solution in the explorer, select Properties.


2. Select the Configuration properties page, and select Build for the installer.

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.

9.2. Alternative installer programs


The 'Express' versions of C# or VB don’t offer the full range of deployment options,
and so the method outlined in §9.1 is unavailable. As you can read express solution
files with the professional versions of Visual Studio, the easiest option is to locate a
friend or colleague with the full version, and get them to create the installer! If that's
not possible, there are a number of commercial and freeware third party installer
tools. Two popular, powerful, and free tools are:

• WiX - Windows Installer XML Toolset (http://wix.sourceforge.net/)


• NSIS - Nullsoft Scriptable Install System (http://nsis.sourceforge.net)

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 54 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

10. A real-life example


The example plugins presented so far in this document have demonstrated the
mechanisms involved. This example uses a more realistic plugin, which creates text as
a pattern or toolpath within PowerMILL. There is a Visual Studio solution for this
example plugin.

10.1. Overview of functionality


The main pane of the plugin allows you to specify any TrueType font and size,
various formatting options, and to enter the text you want to create. The plugin
previews the text within the pane, and has buttons to create the text as a pattern or a
toolpath.

The formatting options for the text include:

• 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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 55 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

10.2. Engraving font format


Engraving font files are placed in the EngravingFonts directory, and have an
extension .eft. The format of these files is XML:
<EngravingFont Name="Tahoma" Height="40" LineSpacing="66.41"
Baseline="55.04" ItalicAngle="18.77">
<Character Unicode="32" Human=" " Width="17.19">
<Geometry></Geometry>
</Character>
<Character Unicode="65" Human="A" Width="32.99">
M25.99571,13.44341 L6.863122,13.44341 M29.60259,2.522739 L17.96617,
37.57792 L14.84362,37.57684 L3.230073,2.413122
</Character>
...
</EngravingFont>

The root element <EngravingFont> has these attributes:

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

Unicode The unicode number (in decimal) of the character.


Human The human readable character.
Width The width of the character, in mm.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 56 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

Code Description

M x,y Start a new curve segment at the specified point.


L x,y Create a line segment from the current point to the
specified point.
C x1,y1 x2,y2 x3,y3 Create a cubic Bézier from the current point to x3,y3,
using the other two points as control points.
Z Close the segment by adding a straight line from the
current point to the start point.

So the XML snippet:


<Character Unicode="65" Human="A" Width="32.99">
M25.99571,13.44341 L6.863122,13.44341
M29.60259,2.522739 L17.96617,37.57792 L14.84362,37.57684 L3.230073,2.413122
</Character>

Defines the character 'A', with a single horizontal line, and then three connected lines
around the perimeter.

10.3. Overview of the Visual Studio solution


The text creation plugin solution contains 4 projects:

1. The plugin itself


2. The framework
3. A host application
4. The installer.

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:

• The buttons at the top


• The main text input box
• The preview region
• The 'create' buttons.

Most of the controls on the pane follow fairly standard WPF principals. But some
parts of the XAML file require further explanation:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 57 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

• 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.

10.4. Command handling


You can drive the text creation plugin by commands. This implementation uses the
utility functions described in §6.10.3. Commands are of the form:
PLUGIN {DB03D7F5-ACEC-4E4D-824B-094C7645975D} ...

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 58 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

where the end of the command (indicated by '...') can be any of:

Command Details

ENGRAVING_FONTS Use engraving fonts, not TrueType.


TRUETYPE_FONTS Use TrueType fonts, not engraving.
FONT "Font name" Specify a font, for example Tahoma.
FONT_HEIGHT real Set the font height.
BOLD ON|OFF Turn bold on or off.
ITALIC ON|OFF Turn italic on or off.
RESET_COLOUR Set the colour to Automatic.
COLOUR short short short Specfies the colour of the text in using R G B
values of 0 - 255.
TEXT "Some text" Set the text for the plugin. This can contain '\n'
to start a new line.
VERTICAL_SPACING real Sets the vertical spacing factor in the range 0.1 -
5.0. 1.0 is normal spacing.
HORIZONTAL_SPACING real Sets the horizontal spacing factor in the range
0.1 - 5.0. 1.0 is normal spacing.
CREATE_PATTERN Creates a pattern.
CREATE_TOOLPATH "tool name" Creates a toolpath with the specified tool, depth
depth tolerance of cut and tolerance.
MIRROR NONE|HORIZONTAL|VERTICAL Sets the mirroring option.
RESET Resets all settings back to their default.

10.5. Importing the geometry into PowerMILL


When you click the Create Pattern or Create Toolpath buttons, the plugin creates a
PIC file containing all of the 2D curve geometry. PowerMILL imports this file as a
pattern. The plugin issues a sequence of commands to achieve this:

• 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:

• Offsetting the pattern in Z for the depth.


• Setting up the block.
• Committing the pattern as a toolpath.
• Deleting the pattern.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 59 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

11. Using PowerMILL without plugins


Even if plugins are disabled, PowerMILL needs to start them to discover their name,
version, description, and minimum PowerMILL requirements to list this information
in the Plugin manager. If you wish to prevent PowerMILL starting plugins at all for
any reason, you can Start PowerMILL in a 'safe mode' where the plugin manager
makes no attempt at all to load anything. The command line option for this is
-safemode.

12. Compatibility with other automation solutions


If PowerMILL is started as an Automation server using an external COM client,
plugins are disabled by default. However, a simple call to EnablePlugins() on the
main PowerMILL COM interface starts the plugin manager, and loads the installed
plugins as normal. Older COM clients and plugins can coexist without interfering
with each other.

13. Writing plugins in VB


Plugins can be written in VB in just the same way as they are in C#. The plugin needs
to implement the same interfaces, or derive from one of the framework base classes.
A typical example of the former would be:
Imports System.Windows.Forms
Imports System.Windows
Imports System.Runtime.InteropServices
Imports Microsoft.Win32

<Guid("81e14895-acc0-43e4-9747-4e29be5c6582")>
<ComVisible(True)>
<ClassInterface(ClassInterfaceType.None)>
Public Class PowerMILLPlugIn
Implements PowerMILL.IPowerMILLPlugin
Implements PowerMILL.IPowerMILLPluginPane
...
End Class

13.1. Displaying WinForm forms


Displaying a WinForms dialog using VB is straightforward:
Dim form As New MyForm
Dim parent_window As New WindowWrapper(New System.IntPtr(m_parent_window))
form.Show(parent_window)

This code relies on a simple window wrapper class:

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 60 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

Public Class WindowWrapper


Implements System.Windows.Forms.IWin32Window
Public Sub New(handle As IntPtr)
_hwnd = handle
End Sub

Public ReadOnly Property Handle() As IntPtr Implements


System.Windows.Forms.IWin32Window.Handle
Get
Return _hwnd
End Get
End Property

Private _hwnd As IntPtr


End Class

13.2. Displaying WinForm Panes


For a plugin to display a pane, it must implement the IPowerMILLPluginPane interface.
Panes are implemented as a class derived from System.Windows.Forms.UserControl.
Initialisation involves the control being parented with the supplied HWND, and can
be performed as follows:
Dim user_control As New MyUserControl

Public Class PowerMILLPlugIn


Implements PowerMILL.IPowerMILLPlugin
Implements PowerMILL.IPowerMILLPluginPane

' Import a required Win32 function


<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function SetParent(ByVal hWndChild As IntPtr, _
ByVal hWndNewParent As IntPtr) As IntPtr
End Function

...

' Implement the pane initialise function


Public Sub InitialisePane(PaneIndex As Integer, ParentWindow As Integer, _
Width As Integer, Height As Integer) _
Implements PowerMILL.IPowerMILLPluginPane.InitialisePane
SetParent(user_control.Handle, New IntPtr(ParentWindow))
End Sub
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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 61 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

13.3. Getting a WinForms TextBox to handle the 'Enter' key


When a TextBox control resides within a non-WinForms dialog, the dialog manager
used will obviously be the Windows 'native' manager, not the WinForms one. In most
instances, this causes no problems, but when it comes to handling KeyDown and
KeyPress events, there is an issue. The Windows dialog manager will interrupt the
relevant messages, so the control never gets to hear about them. Use the following
class as an extended version of the TextBox control, which works around the
problem.
' A 'special' text box, that will receive KeyDown/KeyPress events when the
' parent isn't a WinForms Form, but rather a 'Native/MFC' dialog window.
' The standard Windows dialog manager will suppress the Enter key from being
' processed as it sees it as a management key (e.g. press the button
' with keyboard focus, usually to accept the form). We can get around this
' by listening out for a special dialog message (WM_GETDLGCODE), checking the
' key it relates to (VK_ENTER), and then returning a special code to say that
' this control really does want to receive the key messages (DLGC_WANTALLKEYS).
' In turn, the dialog manager will then send the normal WM_KEYDOWN, WM_CHAR and
' WM_KEYUP messages, which WinForms turns into the 'normal' events that get
' fired.

Imports System.Windows.Forms

Public Class TextBoxEx


Inherits TextBox

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)


' If the message is WM_GETDLGCODE, and the key it relates to is
' VK_ENTER, we need to return the code DLGC_WANTALLKEYS, and don't
' bother calling the base class
If m.Msg = 135 And m.WParam = 13 Then
m.Result = &H4
Else
MyBase.WndProc(m)
End If
End Sub
End Class

14. Handling different system font sizes


PowerMILL is designed to run at a 96dpi (text size 100%), although will display
more-or-less correctly at 120dpi (125%). The plugin mechanism will detect if the
operating system is running at something other than 96dpi, and will scale the size of
any panes and tabs accordingly to accommodate the differing space requirements.

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.

14.1. Scaling panes and tabs using WPF


WPF has been designed to automatically scale any content according to the system
dpi setting, so no action should be required by the developer. The size of any WPF
user controls should simply be set to the scaled values provided by PowerMILL.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 62 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

14.2. Scaling panes and tabs using WinForms


WinForms is an older technology, and thus requires the developer to be a little more
proactive when setting up panes and tabs to work with at different dpi settings.

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;

public PaneResizer(UserControl uc)


{
// Cache the user control
m_uc = uc;

// Get the current dpi


Graphics g = uc.CreateGraphics();
float dpi_x;
float dpi_y;
try
{
dpi_x = g.DpiX;
dpi_y = g.DpiY;
}
finally { g.Dispose(); }

// We assume the original 'design' font size was 8 x 16 pixels,


// at 96dpi.
float orig_dpi_x = 96.0f;
float orig_dpi_y = 96.0f;
float orig_size_x = 8.0f;
float orig_size_y = 16.0f;

// Do we need to resize things?


if (Math.Abs(dpi_x - orig_dpi_x) < 1.0 &&
Math.Abs(dpi_y - orig_dpi_y) < 1.0)
{
m_needs_resizing = false;
}
else
{
// We need to figure out the size of the font we're going to
// pretend it was so that things display correctly.

// Note: we set the size 1% smaller than we calculate it,


// so that the scaling is 1% bigger. This helps us avoid the case
// where the exact calculated size is sometimes 1 pixel too small.
float factor = 0.99f;

// 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;
}
}

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 63 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

public void Resize(int Width, int Height)


{
// Apply the size
m_uc.Width = Width;
m_uc.Height = Height;

// Do we need to resize things to ajust for differences in dpi?


if (m_needs_resizing)
{
// Apply the font size to the user control
m_uc.AutoScaleDimensions = new System.Drawing.SizeF(
m_font_size_x,
m_font_size_y
);

// Instruct the user control to scale itself using the font


// size we've requested
m_uc.AutoScaleMode = AutoScaleMode.Font;
}
}
}

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.

14.3. Forcing panes and tabs to not scale


In some circumstances, you may prefer not to respect the system's font size setting,
and thus do not want PowerMILL to scale panes or tabs. A plugin services method
can be called to disable scaling for your plugin. It must be called within your plugin's
Initialise() method.

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.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 64 of 65
Writing a PowerMILL plugin Developer's documentation
Revision No: 6

15. Revision history


Who When Revision What
PSL 26/08/2011 1 Written.
CMH 12/09/2011 2 Changes to make it more consistent with
PowerMILL documentation.
PSL 06/10/2011 3 Minor corrections, clarifications and added bits on
VB and WinForms.
PSL 07/11/2011 4 Use .NET 4.0.
PSL 06/12/2012 5 Added advice regarding tabs, and debugging using
the surrogate. Added advice regarding WinForm
TextBoxes.
PSL 14/05/2013 6 Added advice regarding scaling with the system
font.

© 2013 Delcam plc., Birmingham, UK 14/05/2013


Commercial confidential Page 65 of 65

You might also like