Work / eplanbridge
eplanbridge
Tovex Dev project - README
Stack
csharp · eplan · eplan-pro-panel · industrial-automation · mcp
Source on GitHub ↗ · Talk to me →
Readme
Drive EPLAN Pro Panel 2022.0.3 from outside the application — and build a 6-axis robot-arm control schematic with it.
A programmable control surface for EPLAN Pro Panel, shaped like the existing gxbridge MCP server, plus the project it was built to produce: RoboArm_6DOF_FX5U, a 20-page schematic for an FX5U-controlled 6-axis arm — 273 symbols and 129 connections, all placed by script.
Read these first
Each records what was measured and what is still a guess. Read the relevant one before changing anything.
Why
EPLAN has no supported way to build a schematic in bulk from outside the GUI. Drawing 273 symbols and wiring them by hand is slow and unrepeatable; doing it from a script makes the drawing a build artefact that can be rebuilt, diffed, and corrected. This project works out what that takes and records the parts that are not obvious — chiefly that placement alone connects nothing.
Why it is built this way
EPLAN's remoting channel exposes exactly one useful verb: ExecuteAction. The interesting API — DataModel, DataModel.E3D, HEServices — is in-process only. The bridge between them is EPLAN's ExecuteScript action.
But EPLAN's script compiler carries no reference to Eplan.EplApi.DataModelu.dll. Any compile-time mention of that namespace fails the whole script, and a failed compile is reported as Succeed=False with an empty message — no error text anywhere. That single fact dictates the design.
PowerShell / MCP server (out of process, net48)
| EplanRemoteClient.ExecuteAction
v
ExecuteScript /ScriptFile:EplanBridgeStub.cs
| (the ONLY reflection in the system)
v
EplanBridge.Core.dll (in process, typed C#, full API access)EplanBridgeStub.cs names no EPLAN API type, so it always compiles. It loads EplanBridge.Core.dll and calls Bridge.Invoke(command, argument). Everything below that boundary is ordinary typed C# — which means the compiler checks the API usage instead of mistakes surfacing at runtime.
The stub loads the DLL from a byte array, not Assembly.LoadFrom. That leaves the file unlocked, so EplanBridge.Core can be rebuilt without restarting EPLAN. Verified working.
Layout
Build
cd src\EplanBridge.Core
dotnet build -c Release -o ..\..\binTargets net48 because the EPLAN API is .NET Framework. The only SDK installed is .NET 10, which cannot load Framework assemblies in-process — but it builds net48 fine, since the targeting pack is present. EPLAN references use false so its DLLs are never copied; the process already has them loaded.
Use
.\tools\Invoke-EplanBridge.ps1 -Command ping
.\tools\Invoke-EplanBridge.ps1 -Command project.info `
-Argument 'C:\Users\Kishor\Tovex-Work\eplan-propanel-bridge\projects\RoboArm_6DOF_FX5U.elk'-Port defaults to 57315 (the dedicated instance). Start a dedicated instance with an explicit port rather than an ephemeral one:
Eplan.exe /Variant:"Pro Panel" EplanServer /EPLANSERVERPORT:<port>Verify
With a dedicated EPLAN instance running on port 57315:
.\tools\Get-ProjectAudit.ps1Success is the last three lines reading exactly:
total functions = 273
total connections = 129
untagged (=+) = 273and every page =RA1+CAB/2 through /17 reporting a non-zero function count. The audit reopens the project from disk, so a passing run is also the persistence proof — there is no Save in this API, only Project.Close().
untagged = 273 is the expected value, not a failure to investigate: device numbering does not work yet (FINDINGS §3). It will drop to 0 when it does.
Parts allocation
.\tools\Invoke-EplanBridge.ps1 -Command project.parts `
-Argument 'C:\Users\Kishor\Tovex-Work\eplan-propanel-bridge\projects\RoboArm_6DOF_FX5U.elk'Success is withoutPart reading 0:
{"totalFunctions":169,"withPart":144,"withoutPart":0,
"cannotCarryArticle":25,"distinctParts":9}totalFunctions is 169 here and 273 in the audit above, and both are correct. The audit sums 16 page-filtered queries; this is one project-wide query, and GetFunctions(null) does not return the 104 PLC connection points. Do not "fix" either number to match the other.
Rebuild from scratch and re-verify
.\tools\Build-RoboArmSchematic.ps1
.\tools\Invoke-EplanAction.ps1 -Actions 'generate /TYPE:CONNECTIONS /PROJECTNAME:"C:\Users\Kishor\Tovex-Work\eplan-propanel-bridge\projects\RoboArm_6DOF_FX5U.elk" /REBUILDALLCONNECTIONS:1'
.\tools\Invoke-EplanBridge.ps1 -Command project.close -Argument 'C:\Users\Kishor\Tovex-Work\eplan-propanel-bridge\projects\RoboArm_6DOF_FX5U.elk'
.\tools\Get-ProjectAudit.ps1Commands
Page types accepted by page.create: Circuit, TitlePage, TableOfContents, PLCDiagram, PLCCardOverview, TerminalDiagram, PanelLayout, Overview, and the rest of DocumentTypeManager.DocumentType.
Rules for adding a command
- One command, one verb, one effect. Never "create or update".
- Return JSON always. Never throw across the boundary; never show a dialog — a modal
dialog blocks every later remote call, since EPLAN only services remote calls when idle.
- Data-model writes must be wrapped in a LockingStep, or EPLAN throws
NoLockingStepException (S063110).
- A project already open cannot be re-opened. Use GetProject first (returns null
when not open), then OpenProject.
- State in the command's docs what it cannot do.
API gotchas found the hard way
There is no Save. The only persistence trigger is Project.Close(). Verified: after creating 19 pages, Page.eod was still 1,134 bytes at its original timestamp; after project.close it became 22,680 bytes, and reopening from disk (wasAlreadyOpen:false) returned all 20 pages. Never report a write as done before closing — an in-memory read-back proves nothing.
Placing symbols does not connect them. EPLAN auto-connects connection points that share an X coordinate and face each other, but only when generate /TYPE:CONNECTIONS /REBUILDALLCONNECTIONS:1 is run. Place → generate → close. Skip the middle step and you get a drawing that looks right and is electrically empty.
Place symbols with SymbolVariant.Create(page), never Function.Create. Function.Create only builds generic Functions. For a symbol backed by a specialised class (terminals, cables, PLC boxes) it creates the object and then throws NotImplementedException — leaving a half-initialised object on the page while reporting failure. Observed exactly: four terminals reported FAIL yet all appeared in page.contents. SymbolVariant.Create returns the correct subclass; set SymbolReference.Location afterwards.
Device tags cannot be set as a property. FUNC_DEVICETAG_FULL throws NotImplementedException; NameParts has no FUNC_DEVICETAG_MAINNAME; there are no IDENTLETTER/COUNTER name-part properties on FunctionBasePropertyList. EPLAN assigns device tags with its own renumber action — place first, number after.
The page description property is PAGE_NOMINATIOMN — EPLAN's own misspelling, documented as "Page description # 11011". There is no PAGE_DESCRIPTION. PAGE_SUPPLEMENTARYFIELD is a different, indexed property (11901) and is the wrong choice.
Read the full README on GitHub →
More work
plc-checkweigher
One-command installer for the Mitsubishi PLC check-weigher system. Python · Raspberry Pi · Mitsubishi PLC · PDF Reports · npm CLI
checkweigher
High-speed industrial check-weigher on the edge. Python · Raspberry Pi 4 · XT1000 · T16 Load Cell
Tovex-CRM
Modular CRM platform with real-time dashboards. TypeScript · TypeScript · Modular Architecture · Real-time Dashboards