Getting started
Last update: 19.06.2026Overview
This guide walks you through setting up your development environment and creating your first parametric analysis component zipped (PACZ) component plug-in. By the end of this guide, you'll have a working plug-in that you can test and extend.
For terminology used in this guide, please refer to the Glossary.
Getting started in .NET
Install the TestUI application
- Start from the
artifactsfolder in theCAMPluginSDKzip file. - Move the downloaded zip file to your development machine.
- Locate the
TestUIfolder which contains the TestUI.exe application needed later. This location is later referred to as[TestUI_Install_Directory].
NOTE: The TestUI is not a production tool. It was written extremely quickly and did not follow the standard software process and as such is funky in a number of ways. However, many people have found it a very useful tool.
Configure Visual Studio to use the NuGet repository
- Inside Visual Studio 2019 or 2022, right-click the solution item in Solution Explorer
- Select "Manage NuGet Packages for Solution…"

- Click the gear icon to bring up settings

- Click the + icon to add a new package source

- Change the Source text field to point to the
artifactsfolder in theCAMpluginSDKZIP file - Change the name field to "Desktop Packages" or any other descriptive string

- After accepting changes, you should now see "Desktop Packages" as an option in the Package Source dropdown menu

- Click "Install" for
Phoenix.ComponentPlugInSDK

Optionally install the PACZ component plug-in wizard for Visual Studio
- Requires Visual Studio 2019 (not yet supported in Visual Studio 2022)
- Adjacent to the TestUI folder is the
ComponentPlugInSDKTemplatefolder - Double-click the
Phoenix.ComponentPlugInSDKTemplateWizard.vsixfile - Complete the VSIX installer wizard
The Wizard creates a stub plug-in project ready to be implemented. If you do not wish to or cannot use it, copy a sample plug-in as a starting place.
Create a plug-in project
In Visual Studio, create a new project.
On the "Create a new project" page:
- Select the template that was installed earlier: "PACZ Plug-In Template"
- Press Next
On the "Configure your new project" page:
- Enter the project name (this is used as the default namespace and plug-in name)
- This name is referred to as
[Name]throughout this document - Choose the project location
- For Framework, choose at least .NET Framework 4.6.2
- Press Create
On the "New PACZ Plug-In Project Wizard" dialog:
- Choose "Variable Based"
- Press OK
Visual Studio creates a new project for you.
NOTE: Visual Studio 2019 has a bug where the project may take some time before it builds as it resolves package dependencies. If the project references do not show Phoenix.ComponentPlugInSDK, try these steps:
- Right-click the solution in Solution Explorer
- Choose "Restore NuGet Packages"
- Close and reopen the solution
Debugging with Visual Studio
After building your plug-in and deploying it to ModelCenter or TestUI, you can debug it using Visual Studio:
- Set breakpoints in your code at key functions like
ConstructAsync,RunAsync,SetupView, etc. - In Visual Studio, go to Debug → Attach to Process
- Find and attach to
Phoenix.OutOfProcessBuilderUIHostorTestUIdepending on how you are running the plug-in - Run your component to trigger breakpoints
NOTE: Breakpoints on ConstructAsync do not work when running from ModelCenter as it is called
immediately after starting Phoenix.OutOfProcessBuilderUIHost, before you can attach to the process.
Custom icons
To add a custom icon to your plug-in:
- Create a new folder
Imagesat the Visual Studio project root level - Copy your icon file to this folder
- Un-comment and rename
[PlugInIcon("Images/placeholder.ico")]in the main plug-in class file
Getting started in Java
TBD - Java plug-in development support is planned for a future release.
New project overview (.NET)
Project files
When you create a new plug-in project, two main class files are generated:
The runner - [Name].cs
This file implements IHarnessRunner:
- Purpose: Executes runs of the plug-in
- Important: There MUST be no UI associated with this class
- Key Methods:
ConstructAsync: Always called first. Provides theIHarnessRunnerHost. Use this for initial setup and loading configuration.RunAsync: Executes a single run evaluation. Receives inputs and must populate outputs.
The builder UI - [Name]BuilderUI.cs
This class extends AbstractVariableBasedBuilderUI<[Name]Runner>:
- Purpose: Provides the user interface for configuring the plug-in
- Features: Builds on a pre-built UI form for allowing a user to select variables from a list of available ones
ComponentNameProperty: Override this to provide a display name for the plug-in- Methods You Can Override:
LoadFromPaczAsync: Load the plug-in instance from a PACZ fileGetTreeProperties: Customize the properties treeGetFileLoadProperties: Configure the built-in file open featureSetupView: Add menu items to the main menuSaveToPaczAsync: Save the plug-in instance to a PACZ file
Useful classes
Some classes and interfaces that you need to work with.
AbstractBuilderUI<RUNNER>
This base class makes writing your own builder UI easier.
Properties:
Hostof typeIHarnessBuilderUIHost<RUNNER>
Methods:
AddMenuItem()andAddAsyncMenuItem(): Add custom menu items (requires reference toPresentationCore)SelectVariables(): Open the variable selection formSetPaczIcon(): Set a custom icon on the current PACZ
IHarnessBuilderUIHost<RUNNER>
This is the host object passed to your builder UI which gives you access to a variety of useful capabilities:
Properties:
ExtractedPacz: The loaded PACZ object and configurationLogger: Logging for the plug-in
Methods:
CallRunnerAsync<T>(): Invoke code on the runnerRaiseTestRunEvent(): Cause a run to be requested from the runnerSaveAsync(): Save the PACZ
VariableBasedBuilderViewModel
This class provides access to the plug-in's variables:
- Provided as an argument after a new file is opened
- Provided to the
SetupView()method - Can be made available to custom menu item click handlers
- Provides collections of
InputVariablesandOutputVariables
Testing the plug-in
Test with TestUI
- Build the plug-in project
- Copy the
Debugfolder (from[PlugIn Dir]/bin) to thePlug-Insfolder of the TestUI App - Run TestUI
- Create a new component
- Navigate to
Plug-Ins / Debug / [Name] - The new component wizard appears (same as in ModelCenter)
After making changes:
- Rebuild the project
- Replace the directory in the TestUI app
- Rerun TestUI
Advanced Tip: Use mklink to create a soft link from the TestUI Plug-Ins folder to your project's compile output folder, eliminating manual copying after each build.
Process Options: By default, TestUI runs plug-ins in-process, which is useful for debugging. To simulate ModelCenter's out-of-process execution, run TestUI with the -oop command line option.
Test with ModelCenter 14.5+
- Install ModelCenter version 14.5 or higher
- Copy the
Debugfolder (from[PlugIn Dir]/bin) to thePlug-Insfolder of your ModelCenter installation
(Typically:C:\Program Files\Phoenix Integration\ModelCenter 14.5\Plug-Ins) - Open ModelCenter and create a new workflow
- Locate your plug-in in the Server Browser under
component plug-in / [your plug-in's name] - Drag the plug-in icon into the workflow to launch the new component wizard
Customizing your plug-in
Creating the UI
Name the plug-in
Override the ComponentName property in your BuilderUI class:
protected override string ComponentName => "My Custom Plug-In";
LoadFromPaczAsync
Called when the plug-in is first created or loaded. Load state from the IExtractedPacz:
- Most state is stored in
Config.Properties(name-value pairs) - Files are stored in
Config.InstanceFiles - Variables are saved/loaded automatically by the
viewModel
The Model
object (type VariableBasedBuilderModel)
is directly accessible and can create variables:
protected override async Task LoadFromPaczAsync(IExtractedPacz extractedPacz)
{
if (Model.InputVariables.Count == 0)
{
// Create the initial set of variables for the view
Model.MoveInputVariablesFrom(_createInputs());
Model.MoveOutputVariablesFrom(_createOutputs());
}
await Task.CompletedTask;
}
private IEnumerable<IRuntimeVariable> _createInputs()
{
RuntimeVariable x1 = new RuntimeVariable("x1", VariableType.Real, new RealValue(0.5));
RuntimeVariable x2 = new RuntimeVariable("x2", VariableType.Real, new RealValue(1.75));
return new List<IRuntimeVariable>() { x1, x2 };
}
private IEnumerable<IRuntimeVariable> _createOutputs()
{
RuntimeVariable y = new RuntimeVariable("y", VariableType.Real, new RealValue(0.01));
return new List<IRuntimeVariable>() { y };
}
GetFileLoadProperties()
Override this method to enable a built-in file load dialog:
- Create a
FileLoadPropertiesobject with an action for when a file is selected - Specify a file filter for the open dialog
- Copy the selected file to
Host.ExtractedPacz.ExtractionFolder - Add it to
Config.InstanceFiles
Example Use Case: Selecting a CAD file, then interrogating it to determine which inputs/outputs to create.
SelectVariables()
Help users create variables programmatically:
- Option 1: Add variables directly to
viewModel.Variablelist - Option 2: Use the
SelectVariablesform for user customization
To use the SelectVariables form:
- Pass lists of available inputs and outputs
- Call
SelectVariables()method of theAbstractBuilderUIobject - Users can filter and select which variables to include
GetTreeProperties()
Allows customization of the variable tree:
-
componentName: Name displayed at the root of the VariableTree -
canAddRemove: Whether UI controls allow adding/removing variables -
hasNamedVariables: Whether variables use named variable properties- Named variables map to external identifiers (for example, Excel range names)
- The variable name in the plug-in might be "cost" while mapping to range "B5:B10"
-
namedVariableDisplayName: Label for the named variable field (for example, "Range") -
namedVariableToken: Alphanumeric token used in the text-based variable editor
SetupView()
This method is called after the form is created and allow customization. This is typically done by adding menu items to the main Menu. By default, VariableBasedPlugIns has an Apply button that saves the plug-in instance. Additional menu items can be added as either buttons or containers of subItems.
Use the AddMenuItem method of the AbstractBuilderUI base class to add items and provide an event handler as the action to perform on click. The VariableBasedBuilderViewModel can be made available to use in the event handler if desired.
Common usages of additional menu items are to show an options dialog or a help page for the plug-in. The following code sample shows a simple Windows Form made accessible through a custom options menu item. No further code is required to make a Form1 object accessible to the user.
protected override void SetupView(IExtractedPacz pacz,
VariableBasedBuilderViewModel viewModel,
Menu mainMenu)
{
AddMenuItem(parent: mainMenu,
header: "Options",
imageType: ImageType.OPTIONS,
hasDownArrow: false,
eventHandler: (s, e) => _editOptions(viewModel));
}
private void _editOptions(VariableBasedBuilderViewModel viewModel)
{
Form1 form1 = new Form1();
form1.Show();
}
Common Uses: Options dialogs, help pages, custom wizards
SaveToPaczAsync()
Save plug-in state to the PACZ file:
- Store settings as name-value pairs in
pacz.Config.Properties - Update instance files if needed
- Variables are saved automatically by the
ViewModel- typically no action is needed
Creating the runner
ConstructAsync()
Always called first - use for initialization:
- Capture the
IHarnessRunnerHostfor later use - Load configuration
- If using resource-intensive applications, consider delaying load until first run
private IHarnessRunnerHost _host;
private VariableBasedBuilderModel _model;
public async Task ConstructAsync(IHarnessRunnerHost host)
{
_host = host;
_model = new VariableBasedBuilderModel();
await _model.FromPaczAsync(_host.ExtractedPacz);
//TODO: _model.FilePathAbsolute will now contain the full path to the file the use chose
// when building the pacz, or null if the pacz is uninitialized.
}
RunAsync()
Execute a single run evaluation:
- Input variables are passed with values and validity flags
- Use
SafeValueto ensure all inputs are valid (throws if invalid) - Update the
outputsdictionary with newVariableStatefor each output
public async Task RunAsync(IReadOnlyDictionary<string, VariableState> inputs,
VariableValueScope outputs,
CancellationToken cancellation)
{
// Get input values
double x1 = (RealValue)inputs["x1"].SafeValue;
double x2 = (RealValue)inputs["x2"].SafeValue;
// Perform computation
double result = x1 * x2;
// Set output values
outputs["y"] = new VariableState(new RealValue(result));
await Task.CompletedTask;
}
Next steps
Now that you have a working plug-in:
- Explore the FAQ for answers to common questions
- Review the Threading guide for understanding thread safety
- Study the example plug-ins in the repository
- Learn about advanced customization options
For common issues and solutions, see the FAQ page or consult the threading documentation.