Icon packs: PLC Icons
Icon packs come only with official plugins
An icon pack is code that runs inside the server, so it can only be a C# plugin, and the server loads C# plugins only when they are official and signed. JavaScript plugins cannot add icon packs. This page shows how the official PLC Icons plugin is built, for reading.
An icon pack is the simplest useful C# plugin: no connection, no settings, no actions. The PLC Icons plugin (PLCIcons/, id plc-icons) contributes 29 ladder-logic (PLC) symbols to the editor's icon picker as their own category. This page walks through it.
What a pack is
Implement IIconPackSource and register it in Initialize with host.RegisterIconPack:
| Member | Meaning |
|---|---|
Id | Unique id of the pack. |
DisplayName | The category name in the icon picker. |
IconNames | The names of the icons, in the order you want them listed. |
GetIconSvg(name) | The SVG text of one icon, or null if there is none. |
The whole plugin
using MacroGrid.Plugin.Abstractions;
namespace MacroGrid.Plugin.PlcIcons;
/// <summary>Entry point (plugin.json's "entry") — the host finds this type by reflection and instantiates it
/// with a parameterless constructor, then calls <see cref="Initialize"/> once.</summary>
public sealed class PlcIconsPlugin : IPlugin
{
public void Initialize(IPluginHost host) => host.RegisterIconPack(new PlcIconPack());
}
/// <summary>A static icon pack: ladder-logic function block icons (coil, timers, comparisons, arithmetic),
/// embedded as SVG resources in this assembly. No connection, no settings — the icons never change at
/// runtime, so <see cref="IIconPackSource"/> is the whole plugin.</summary>
public sealed class PlcIconPack : IIconPackSource
{
// Sorted so the picker's default ordering doesn't depend on the filesystem's own order.
private static readonly string[] Names =
[
"add", "calculate", "close-branch", "coil", "convert", "divide", "empty-block", "equal",
"f-trig", "greater", "greater-equal", "lesser", "lesser-equal", "move", "multiply", "n",
"nc", "no", "not-equal", "open-branch", "p", "r-trig", "reset-coil", "set-coil", "subtract",
"timer-convert", "tof-timer", "ton-timer", "tp-timer",
];
public string Id => "plc-icons";
public string DisplayName => "PLC Icons";
public IReadOnlyList<string> IconNames => Names;
public string? GetIconSvg(string name)
{
if (Array.IndexOf(Names, name) < 0) return null;
var assembly = typeof(PlcIconPack).Assembly;
using var stream = assembly.GetManifestResourceStream($"MacroGrid.Plugin.PlcIcons.icons.{name}.svg");
if (stream is null) return null;
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}PlcIconsPluginis theIPluginentry point: one line,host.RegisterIconPack(new PlcIconPack()).- The names are sorted so the picker's ordering does not depend on the file system.
- The SVGs are read from resources embedded in the assembly, so there are no loose files at run time.
The display name is written in English (the plugin's default language); the Turkish name "PLC İkonları" comes from locales/tr.json.
The project
The icons live in src/icons/ and are embedded with one line in the .csproj:
<EmbeddedResource Include="icons\*.svg" />The resource name is <RootNamespace>.icons.<file name> (MacroGrid.Plugin.PlcIcons.icons.coil.svg), which is what GetManifestResourceStream asks for above. plugin.json is copied to the output like in any C# plugin:
{
"id": "plc-icons",
"name": "PLC Icons",
"description": "A static icon pack: 29 ladder-logic symbols in their own category in the editor's icon picker.",
"author": "Deccoyi",
"homepage": "https://github.com/Deccoyi/macro-grid-plugin/tree/main/PLCIcons",
"defaultLanguage": "en",
"version": "0.1.4",
"minMacroGrid": "1.0.0",
"sdkVersion": "^0.4.0",
"minServerVersion": "0.3.2",
"entry": "MacroGrid.Plugin.PlcIcons.dll",
"kind": "csharp",
"permissions": null
}An icon
Each icon is a monochrome SVG that draws with stroke="currentColor". The editor colors the icon by setting color on the root <svg> element, so never hard-code colors. This is coil.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="30" fill="none" viewBox="0 0 40 30"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M0 15h12m16 0h12M16 5c-5 5-5 15 0 20m8-20c5 5 5 15 0 20"/></svg>Adding an icon
- Put a new
.svginsrc/icons/. Keep it monochrome and usecurrentColorfor every stroke and fill. - Add its file name (without the extension) to the
Namesarray. - Build and reinstall (or reload) the plugin.
