main
History Download
John Lauer 0.2.21 source: script-error auto-recovery + errorShot, symbol_prims/set_models fix for AD 26.9.1, ENGINE_HALTING releas… a577bb1 1mo ago

name: altium-bridge-plan user-invocable: false description: >- THE plan of record for the Altium Designer bridge (keyword altium, verbs altium_*) — every automation surface Altium exposes, measured on a real AD 26.9.1 install, ranked into lanes: the ScriptingSystem:RunScriptText eval, the resident DelphiScript agent (the REPL lane), the 1,109-command process-launcher catalog, X2.EXE -R command-line bootstrap, UIA over the VCL/DevExpress shell (and CDP for its WebView2 pages), the Altium 365 GraphQL API + embeddable viewer, file-format parsers, the native SDK extension, and the sign-in/license gates. Includes the verb catalog, the dialog doctrine, the phase plan with acceptance criteria, and the Phase 0 experiments still owed. Refer back here before any altium-bridge work. Trigger words — altium bridge, altium_ verbs, drive altium, control altium designer, altium automation, altium delphiscript, RunScriptText, altium process launcher, altium 365 api, altium sign in license, altium add-in, altium plan.

Parent skill: adom-bridge-sdk-new-app (the method) and adom-bridge-sdk (the contract). Written 2026-08-29 from live probes of Altium Designer 26.9.1 on a Windows test machine (driven from an Adom container) plus the vendor docs and the community MCP bridges.

altium-bridge — plan of record

0. What we measured on the box (evidence, not guesses)

Fact Evidence
Install C:\Program Files\Altium\AD26\X2.EXE, 26.9.1 (Build 10). System\ holds 634 DLLs, 47 .rcs process tables, 71 .ins server descriptors. No Examples\Scripts shipped.
Process X2.exe pid 54932, main window class TDocumentForm, title License Management - Altium Designer (26.9.1) - No License.
Script engine ScriptingSystem.dll + ScriptingSystem.ins declaring commands RunScript, RunScriptFile, RunScriptText; EditScript.dll/.rcs (script editor); Altium.EditScript.Interfaces.dll; OUT_Script.dll. Prefs page Scripting System → Global Projects / Form Designer.
Command catalog 3,205 process launchers → 1,109 distinct Server:Command strings in the .rcs files: CAMTASTIC 230, PCB 211, Sch 190, TextEdit 100, WorkspaceManager 87, Client 50, SimView 44, IntegratedLibrary 28, UnifiedComponent 26, VersionControl 23, PCB3D 13, … Format: PL <id> Command='PCB:Zoom' Params='Action=...|...' Shortcut1='...' End.
Listening ports none while idle. No application COM automation server registered — only document ProgIDs (AltiumLibrary, AltiumDraftsmanDocument, …).
UIA The VCL/DevExpress shell IS exposed: 45 nodes on the licence screen — MainMenu MenuItems (File/View/Project/Window/Help/Preferences…), TGUIDocBarButtons, TdxTabSheet panes, all with automationIds and invokable:true. The Home / License Management page content is a WebView2 (ProgramData\...\WebView2Cache exists) — its Start Free Trial / Get License / Sign Out buttons are not in the UIA tree.
State dirs %APPDATA%\Altium\Altium Designer {ED62C102-…}\ (prefs), %APPDATA%\Altium\AD26\, C:\ProgramData\Altium\Altium Designer {ED62C102-…}\Extensions\ (Altimade, Draftsman, IPC Footprint Generator, EXPORT3D, IPC2581, SchLibGenerationTool, …), HKCU\Software\Altium\Altium Designer {ED62C102-…}.
Auth state Signed in to an Altium account, no license; a free Altium Designer Viewer seat is offered and a 15-day trial button exists. Vendor docs: Viewer mode prohibits scripting → licence is a hard prerequisite for every in-app lane.

1. The surfaces, ranked into lanes

L2 — the REPL lane: ScriptingSystem:RunScriptText + a resident DelphiScript agent ⭐

Altium has a literal eval: process ScriptingSystem:RunScriptText with parameter Text=Begin … End; (vendor docs, and the string is in ScriptingSystem.dll). Plus RunScript(ProjectName=…|ProcName=…) and RunScriptFile. DelphiScript reaches the full DXP API (PCBServer, SchServer, GetWorkspace, Client, RunProcess), so this is Fusion's run_modeling_script equivalent.

Design (the same shape the two best community bridges converged on — salitronic/eda-agent ~400 tools, coffeenmusic/altium-mcp):

  1. Ship Altium_Agent.PrjScr (+ json_utils.pas, dispatcher.pas) in the Release zip; install it as a Global Project (Scripting System prefs) so it survives restarts.
  2. Bootstrap from the bridge with the CLI lane: X2.EXE -RScriptingSystem:RunScript(ProjectName="…\Altium_Agent.PrjScr"^|ProcName="StartAgent").
  3. StartAgent runs a polling loop on the UI thread with Application.ProcessMessages yields (eda-agent measured ~10 ms/call active, 100 ms idle polling; auto-stop after 10 min idle unless the bridge keep-alives every 30 s). Transport: file drop request_<id>.json → response_<id>.json under the bridge cache (works in any DelphiScript); Phase 0 experiment: CreateOleObject('MSXML2.ServerXMLHTTP') for a direct HTTP long-poll to the bridge.
  4. altium_eval {text} = the agent runs RunProcess('ScriptingSystem:RunScriptText', 'Text=…') (or writes a temp .pas and RunScriptFile) and returns stdout captured to a file. Everything else — get_state, place_component, run_drc, create_footprint — is a canned eval with rich hints.

Known engine traps (all from the community bridges, all to design around, not discover again):

  • DelphiScript is untyped: no records/classes/pointers; single-threaded; a runtime error ("Undeclared identifier", "Could not convert variant of type (Dispatch)") pauses the debugger and every later run silently no-ops until it is stopped. The bridge must detect this (heartbeat file stops updating) and recover it (UIA-click Stop in the Script IDE, or Client:CloseDocument the script; then re-bootstrap).
  • Characters above U+00FF become ? on the way in — ASCII-escape JSON.
  • While the loop is resident, Altium's own script-driven buttons are unresponsive; expose altium_agent_detach.
  • No scripted ECO executor: proj_sync_pcb runs the GUI ECO and blocks on its dialog → L4.

L3 — the process-launcher lane (1,109 commands, no UI, no focus)

RunProcess('PCB:DesignRuleCheck'), RunProcess('WorkspaceManager:Compile'), Client:OpenDocument, Client:CloseDocument, PCB:SwitchTo3D, PCB:Zoom Action=All, Sch:AddComponentPart, WorkspaceManager:GenerateReport ObjectKind=OutputBatch|Action=Run (the OutJob runner from Altium's own CI article). altium_run_command {command, params} exposes the whole table; altium_describe ships the catalog parsed from the .rcs files on the box (coffeenmusic/altium-mcp keeps one as dev/altium_process_catalog.txt). Deterministic and background — prefer it over any menu click.

L6 — the CLI lane (bootstrap + batch)

X2.EXE "<doc>" opens documents; -R<Server>:<Command>(Param="v"^|Param2="v") remote-executes a process. Altium's CI recipe: run a script that opens Build.OutJob, runs the OutputBatch, then TerminateWithExitCode(0). Phase 0 experiment: confirm -R… targets the running instance (single-instance forward) vs spawning a second X2.exe, and whether -RScriptingSystem:RunScriptText(Text="…") works straight from the shell (that would make bootstrap one line).

L4 — UIA over the VCL shell; CDP for the WebView2 pages

desktop_ui_tree sees menus, doc-bar buttons, panels, status bar → desktop_ui_click / desktop_ui_set for Preferences (incl. Scripting System → Global Projects install), dialogs, the Projects/Components panels. The Home/License pages are WebView2: launch X2 with WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=<n> (Phase 0 experiment) and drive them with CDP (nbrowser/pup-style), else image-space clicks with coordMap.shotId. Dialog handling per fusion-driving: scan every window owned by the X2 PID, classify by title, screenshot each, pre-empt what can be pre-empted (save first; pass every parameter; Client:CloseDocument with explicit params), and treat any sweep as a logged defect.

L5 — Win32 lane

PostMessage(WM_COMMAND) after a GetMenu walk for cold-start actions before the agent binds; PostMessage keys to exact hwnds; never SendInput/SetCursorPos on a default path. Altium does not run elevated, so ab's synthetic input reaches it — but it is still the last rung.

L1 — Altium 365 cloud lane (works with the app closed)

GraphQL at https://{workspace}.altium.com/api/graphql/ (Voyager at /api/voyager, Nitro IDE), Authorization: Bearer <token>; tokens from Admin → Developer (1-year access tokens, or refresh + client credentials). Bounded contexts: Design (projects, variants, releases, manufacturing packages), Library (components, symbols, footprints, part requests), Procurement (BOMs), Platform, Collaboration (comments, tasks), Customization (workflows, scripts, script executions — cloud-run scripts). Reads and writes. Plus the embeddable viewer (viewer.altium.com/client/static/js/embed.js, data-project-src=<zip url> or data-project-token) renders schematic/PCB/3D/BOM from a design zip — a render lane that needs no desktop. Verbs: altium_a365_search/browse/project/bom/component/release/run_script, altium_web_preview. Unknowns to verify: plan gating for tokens on a trial workspace; rate limits.

L0 — file lane (no app, no licence)

.PcbDoc/.SchDoc/.PcbLib/.SchLib are OLE-CFB containers; ASCII PcbDoc exists. Parsers: KiCad's Altium importer (best maintained; dev-docs.kicad.org/en/import-formats/altium/), vadmium/python-altium (schematic), thesourcerer8/altium2kicad, AtoK, PyAltium (alpha), embedded-society/altium-designer-mcp (writes PcbLib/SchLib primitives). Use for: altium_state evidence when the app is closed, lint, library generation (IPC-7351 from the Adom molecule pipeline), and KiCad-side previews.

L7 — the native SDK extension (Phase 5, optional)

Altium Developer extension → SDK at C:\ProgramData\Altium\Altium Designer {…}\Extensions\Altium Developer\SDK; DLL + .ins + .rcs; Delphi XE2+/VS2010+ C++/C#. A C# server DLL hosting an in-process HTTP listener would be the Fusion-grade add-in (no file polling, real threads marshalled to the UI). Costs a licensed AD + a build toolchain; do it only after L2 proves insufficient.

2. Authentication & licensing — the gates, in order

  1. Altium account sign-in — system browser OAuth (email / Google / SSO). Drive with nbrowser/pup from the bridge; the user's browser is usually already signed in to their identity provider. Human step: password/2FA only.
  2. Licence — On-Demand / Standalone / Trial (15 days) / Viewer (no scripting). The trial button lives in the WebView2 licence page → CDP or image click, one toast to the user because it is a contractual click. altium_readiness must report licensed:false with the exact next verb, and refuse in-app lanes while unlicensed (they fail silently otherwise).
  3. Altium 365 API token — Admin → Developer in the workspace web UI; drive it, store the token under %LOCALAPPDATA%\Adom Bridge\bridges-cache\altium\a365.json, never in the repo.
  4. Scripting enablement — verify whether AD 26 gates DelphiScript behind a preference (Legacy.Scripts.SupportOldLanguages exists for VBScript/JScript; check for a DelphiScript gate) and whether a Global Project can be registered by writing the prefs file directly.

3. Verb catalog (v1 target)

Group Verbs
Contract altium_readiness altium_status altium_describe altium_state altium_progress altium_uninstall altium_diagnostics
Lifecycle altium_launch altium_close altium_signin altium_license {trial|viewer|ondemand} altium_agent_start/stop/detach
REPL altium_eval {text} altium_run_command {command, params} altium_run_script {prjscr, proc}
Documents altium_open_project altium_open_document altium_close_document altium_save altium_compile
Eight surfaces altium_show_symbol _footprint _3d_chip _library _schematic _2d_board _3d_board _project
Evidence altium_screenshot_all altium_window_info altium_check_dialogs altium_dismiss_dialogs
Outputs altium_run_outjob altium_export_gerber/odb/ipc2581/step/pdf/bom altium_run_drc altium_run_erc
Libraries altium_create_symbol altium_create_footprint altium_import_library altium_list_library altium_install_library_bundle
Cloud altium_a365_* altium_web_preview
Demo altium_demo (symbol → footprint → 3D part → schematic → 2D → 3D board, one beat per call, same shape as kicad_demo/fusion_demo)

Every verb: _hint/_next, stable errorCode (host_app_not_installed, not_licensed, agent_not_running, agent_wedged, foreground_reason_required, …), windowEtiquette, dialogsDetected/dialogs[], measured progress on anything > 3 s, caller identity forwarded on every ab callback with X-Adom-Caller-Delegate: altium.

4. bridge.json essentials

{ "name":"altium", "displayName":"Altium Designer", "spawn":{"kind":"python","entrypoint":"server.py","port":0,"persistent":true},
  "verbPrefixes":["altium_"], "statusVerb":"altium_status", "uninstall":"altium_uninstall",
  "detect":{"hostApp":"Altium Designer","appPathsExe":"X2.EXE","paths":{"windows":["%ProgramFiles%\\Altium\\AD*\\X2.EXE"]}},
  "timeouts":{"default":60,"verbs":{"launch":240,"run_outjob":600,"export_step":360,"demo":300}},
  "docs":"https://wiki.adom.inc/adom/altium-bridge" }

Python binds ADOM_BIND_HOST; scratch under %LOCALAPPDATA%\Adom Bridge\bridges-cache\altium\ with the kicad retention rule (48 h / 300 files / oldest-first / 10-min rate limit).

5. Dialog doctrine for Altium (pre-empt, never sweep)

Commands that prompt → the pre-emption: closing a dirty doc → WorkspaceManager:SaveObject first or Client:CloseDocument with SaveMode; ECO → run compile first and drive the ECO dialog via UIA with a classified catalog; OutJob overwrite → pre-delete outputs; "Confirm" style boxes from DelphiScript (ShowMessage) → never call them in the agent; Script IDE debugger halt → detect by heartbeat and recover. Keep handlers/dialog_classify.py with title → category → resolution.

6. Phases + acceptance

  1. Phase 0 experiments (owed, on a LICENSED AD; ~1 day): (a) trial licence via CDP/image click with toast; (b) -R forwards to running instance?; (c) RunScriptText from CLI?; (d) DelphiScript CreateOleObject('MSXML2.ServerXMLHTTP')?; (e) ProcessMessages loop keeps UI responsive?; (f) WebView2 --remote-debugging-port honoured?; (g) Global Project registration by prefs-file write?; (h) runtime-error recovery path; (i) ASCII PcbDoc export via PCB:Export; (j) A365 token on a trial workspace.
  2. Skeleton (1–2 days): bridge.json, server, readiness/status/describe/state/uninstall/diagnostics, screenshot array by PID, rcs catalog parser. Green chip on a fresh VM; state returns real shots.
  3. Bootstrap + REPL (3–5 days): agent PrjScr, Global Project install, CLI bootstrap, file-drop transport, eval, run_command, heartbeat + wedge recovery. 100 sequential evals, p50 recorded.
  4. Eight surfaces + demo (3–5 days): each show_* background + frame; altium_demo. Web-control page renders all eight.
  5. Outputs + libraries (5 days): OutJob runner, exports, DRC/ERC, symbol/footprint creation, library bundle install from the wiki, dialog catalog. Adom molecule round-trip: wiki part → Altium library → board → Gerber.
  6. Cloud + auth (3 days): sign-in + licence driven with one toast, A365 verbs, web preview.
  7. Publish: Release zip + manifest, skills pkg (altium-bridge core + altium-driving, altium-libraries, altium-a365, altium-signin sub-skills), 30–40 discovery triggers, hero, make release, audit checklist. Then (optional) Phase 5: C# SDK extension.

6b. Phase 0 RESULTS (measured 2026-08-29, AD 26.9.1 Trial) — the facts the code is built on

Experiment Result
Trial licence Start Free Trial (WebView2, image click) → Edge opens altium.com/365-intro/activate-add-eval → workspace form (name, region, terms, Activate) → a workspace is provisioned at <name>.365.altium.com in ~60 s → back in AD: Refresh, then Use License on "Altium Designer Develop · On-Demand · Trial · 30 days". Title flips to … - Trial.
-R forwarding A second X2.EXE -R<Server>:<Cmd>(…) forwards to the running instance and exits in ~2–8 s. If the instance is busy (any modal, or a script running) the helper hangs and shows its own "There is another instance … busy" Information dialog — kill it.
RunScriptText Works from the shell: -RScriptingSystem:RunScriptText(Text="Begin RunApplication('…'); End;"). Statements only — a Var block is "Unknown identifier: Var".
RunScript -RScriptingSystem:RunScript(ProjectName="…\X.PrjScr"|ProcName="Main") works with a plain | when the arg is passed VERBATIM (PowerShell Start-Process, or Python Popen("<one string>")). Popen([exe, arg]) re-quotes (the path has a space) and the helper never forwards. ^| is only for cmd.exe.
Concurrency One script at a time. A -R script while the agent loop is resident raises Error: Another script executing now on the MAIN window; every later -R is refused until it is dismissed.
Late binding An unknown identifier (CreateOleObject, RunProcessWithParameters) is a runtime error: Error: Undeclared identifier … modal + the Script editor opens with the debugger halted on that line. The halted script counts as executing.
Recovery Press OK on the Error (background BM_CLICK), then X2.EXE -REditScript:Stop (helper exits ≈1 s), then scripts run again. Implemented as altium_agent_recover.
CreateOleObject Not available → no HTTP from inside. Transport is file drop.
FindFirst('request_*.txt') Never matched from the engine. Use ONE fixed inbox request.txt (atomic tmp+rename, serialized by the bridge).
RunProcess ResetParameters; AddStringParameter(k,v)…; RunProcess('Client:ArrangeAllWindows') works from the loop: 828 ms in-agent.
Agent loop Application.ProcessMessages + Sleep(10) (100 ms idle) keeps AD responsive; heartbeat hb.txt every 500 ms; start → heartbeat 1.5 s; eval round trip 0.7 s; run_command 1.4 s. RunScript opens the .PrjScr as a document in the Projects panel (harmless).
ab spawn Port 47311 is in a Hyper-V reserved range (WinError 10013) — always --port 0/ab-assigned. shell_execute does not return while a process it started (the server) is alive — start long-lived processes via ab's spawn/launch_process, never a foreground shell.

The bridge source lives in ~/project/altium-bridge/src (server.py, handlers/agent.py, handlers/dialogs.py, handlers/rcs_catalog.py, handlers/altium_detect.py, agent/AdomAgent.pas.tmpl).

6c. Phase 1–3 RESULTS (2026-08-29 evening) — what is DONE

  • ab adopted the bridge from bridges-cache\altium after refresh_bridges {name:"altium"}; it spawns on stable port 64070; adom-bridge altium_* works from the container. bridge_kill {name,confirm:true,reason} reloads code.
  • All eight surfaces return real frames (base64 PNG of the main window, background, foreground restored): show_project 6.7 s, show_schematic 6.8 s, show_2d_board 8.7 s, show_3d_board 14.5 s, show_library 21.6 s (39 components listed), show_symbol 18.4 s, show_footprint 5.1 s warm (a cold PcbLib open blew ab's 60 s default — declare timeouts and expect it), show_3d_chip 16.4 s. altium_demo {stage} chains them.
  • Agent lane: eval 0.7–1.1 s, run_command ~1.4 s, lib_list / lib_select via SchServer.GetCurrentSchDocument + SchLibIterator_Create / PCBServer.GetCurrentPCBLibrary.GetComponent(i) + CurrentComponent.
  • Opening docs: Client.OpenDocument('', path) returns Nil on AD 26 — use ResetParameters; AddStringParameter('ObjectKind','Document'|'Project'); AddStringParameter('FileName',path); RunProcess('WorkspaceManager:OpenObject'), then Client.GetDocumentByPath + Client.ShowDocument.
  • Three real-world races found and fixed: (1) a busy loop (long RunProcess) has no heartbeat → never auto-start a second loop (alive = hb < 60 s, responsive = < 3 s); (2) a killed -R helper's request is still DELIVERED later by Altium → reap stray X2 helpers before every start; (3) tasklist took 7 s under load → Toolhelp32 snapshot in-process.
  • Dialog text is PAINTED (no Static child, not in UIA) — altium_check_dialogs attaches a base64 shot per dialog; OK buttons are TXPBitBtn (BM_CLICK) with ab desktop_ui_click as fallback.

6d. Phase 4 RESULTS (2026-08-29 night) — outputs lane

  • altium_compile / altium_run_erc: WorkspaceManager:Compile Action=Compile|ObjectKind=Project, then read the Messages panel via GetWorkspace.DM_MessagesManager → MessagesCount, Messages(i).MsgClass/Source/Document/Text (DM_MessageCount does NOT exist). 1.7 s. Declare the manager as IDMMessagesManager (that type name compiles); IMessagesManager/IMessageItem as Var types give "Error in declaration block".
  • altium_run_outjob {outjobPath, medium} → WorkspaceManager:OpenObject the .OutJob, then WorkspaceManager:GenerateReport Action=Run|ObjectKind=OutputBatch|OutputMedium=<name>|DisableDialog=True. Fabrication.OutJob → Gerber + NC Drill + ODB++ + DRC report in 23.6 s; Assembly.OutJob → BOM .xlsx + pick-and-place in 16.6 s. Output lands in Project Outputs for <project> next to the project. altium_export_gerber / altium_export_bom are those two mediums.
  • altium_run_drc: PCB:DesignRuleCheck opens a MODAL "Design Rule Checker [mm]" (automationId Run_Button = "Run Design Rule Check...") — the agent blocks inside RunProcess, the bridge clicks the button through ab UIA in the background, then reads violations off the board (BoardIterator filtered eViolationObject, V.Rule.Name, V.Description).
  • Script-engine gotchas that cost the most time: (1) never nest { } inside a brace comment — the inner } ends the comment and the rest becomes code ("Error in declaration block"); (2) Altium keeps the .PrjScr/.pas OPEN in its Script editor after RunScript and re-runs the STALE in-editor copy after you overwrite the file — the bridge now renders AdomAgent_<digest>.pas/.PrjScr per source change; (3) a failed RunScript target opens the "Select Item To Run" picker (TForm_RunScriptParameters) and disables the main window — detect modals by "main window disabled + another enabled top-level window of the same PID", not by class.
  • Foreground: the -R bootstrap activates Altium; the bridge captures the user's foreground before and hands it back immediately and during the wait (John: "stop foregrounding on me"). Restart the agent as rarely as possible — every restart is one activation.

6e. Phase 4 COMPLETE (2026-08-29) — library authoring

  • altium_create_footprint {name, libraryPath, pads:"des,x,y,w,h,rect|round,hole;…"} — opens or CREATES the .PcbLib (CreateNewDocumentFromDocumentKind('PCBLIB') + Doc.DoSafeChangeFileNameAndSave(path,'PCBLIB')), PCBServer.CreatePCBLibComp, pads via PCBObjectFactory(ePadObject…) + Comp.AddPCBObject, PLib.RegisterComponent, CurrentComponent, Board.ViewManager_FullUpdate. 3.1 s, returns a frame.
  • altium_create_symbol {name, libraryPath, designator, bodyWidthMil, bodyHeightMil, pins:"des,name,left|right|top|bottom,offset;…"} — SchObjectFactory(eSchComponent), eRectangle body, ePins with Orientation/PinLength, SLib.AddSchComponent + RobotManager.SendMessage(... SCHM_PrimitiveRegistration ...). 1.1 s.
  • altium_save — WorkspaceManager:SaveObject ObjectKind=FocusedDocument. Both libraries verified on disk.
  • Release kit in the repo: SKILL.md (user skill), README.md, package.json (39 discovery triggers), install.sh/uninstall.sh, CHANGELOG.md, scripts/release.py (runtime zip 52 KB + manifest). NOT yet published — the wiki already has Barrett's barrett-land/adom-desktop-altium-bridge using name altium/prefix altium_; resolve rename-vs-supersede first.

6f. Barrett merge + taskbar (2026-08-29/30)

Ported from barrett-land/adom-desktop-altium-bridge (v0.0.55) into the resident agent — all measured working: altium_set_params (parameters incl. LCSC Part #, comment, footprint link via ISch_Implementation.AddDataFileLink), altium_add_body (extruded contour or ModelFactory_FromFilename STEP), altium_install_library (IntegratedLibraryManager.InstallLibrary), altium_compile_intlib / altium_extract_intlib (CreateIntegratedLibrary / ExtractSources). His AdomBridge .NET extension (prebuilt DLL + .ins/.rcs + Install-AdomBridge.ps1 + REMOVE cmd) is vendored under src/extension/ and exposed as altium_install_extension (Altium CLOSED), altium_extension_ping, altium_export_step (headless STEP with Holes=All|Copper|Models), altium_import_kicad (no-wizard import). Dispatch is X2.EXE -RAdomBridge:<Process>(Req=<space-free request file>) — a server process, not a script, so it coexists with the resident agent. Facts from his page worth keeping: -R splits on whitespace (parameters go in a request file), OutJobs only generate when they are project members, STEP via OutJob no-ops headlessly, Client.ShowDocumentDontFocus exists (the agent's open now prefers it).

Taskbar: handlers/taskbar.py — overlay badge via ab desktop_taskbar {overlay:{icon}} on the first driving verb (cleared on idle), measured-estimate progress bar for verbs ≥3 s, no AUMID stamping. altium_state.drivingBadge reports {active, lastError}.

6g. Phase 5 RESULTS + the extension verdict (2026-08-30)

  • AdomBridge .NET extension (Barrett's) does NOT load into the running AD 26.9.1 instance. Registered via his Install-AdomBridge.ps1 (registry node added cleanly, 20 items), Altium restarted → a Could not start Vault Explorer warning at startup; X2 -RAdomBridge:Ping helpers waited ~65–90 s on the startup mutex and then hosted the server in themselves (their own DXP_Startup.log says "Starting server AdomBridge"), the main instance never did; RunProcess('AdomBridge:Ping') from the resident agent returned in 16 ms and wrote nothing (unknown process → silently ignored). Unregistered again (altium_install_extension {remove:true}). Verbs kept, fail with extension_timeout + this note. Next step if wanted: rebuild against AD 26's in-box .NET assemblies (Barrett's adom-altium-extension project) and verify with a clean-profile Altium.
  • Altium 365 API on the Develop trial: Admin shows Settings / Part Providers / Explorer / Usage and Billing / Extensions — no Developer page, every guessed token URL 404s. altium_a365_* verbs are wired for a token from a Pro/Enterprise workspace.
  • Account/licence: altium_signin_status, altium_license {action} (image-space clicks on the WebView2 page with measured coords: refresh (1288,178), use_license (1252,388), start_trial (788,607) on a 1400×840 capture — verify against the returned shot), altium_connect_workspace (opens the Not Connected picker, TWorkspacesForm, custom list without UIA children → returns a shot to click).
  • Saving example projects on close: Altium asks "Confirm Save Locally for (N) Modified Documents" (WPF grid: Save All / Save None / OK / Cancel, all UIA-invokable) — altium_close reports it; answer Save None + OK for scratch work.

6h. First end-to-end demo — Ray's CH340G (2026-08-30) + two bugs found

Imported ray/ch340g (WCH USB-serial, SOP-16) from the wiki into a NATIVE Altium library entirely through the bridge, then walked the surfaces. WORKED, all of that chip: altium_create_footprint (16 SOIC pads parsed from the page's pads.json, mm→mil), altium_add_body (the page's real 217 KB KiCad STEP), altium_create_symbol (16 named pins GND/TXD/RXD/UD+/VCC/…), altium_set_params (MPN/mfr/pkg + footprint link), altium_show_symbol / _footprint / _library, altium_place_on_pcb (new place_on_pcb verb: IPCB_LibComponent.Replicate → Board.AddPCBObject → the footprint lands on a real PcbDoc, 16 pads), altium_show_2d_board.

TWO REAL BUGS surfaced — fix before publishing:

  1. place_on_sch loses the symbol graphics. ISch_Component.Replicate onto a fresh sheet placed the component (U1 designator + CH340G comment appear) but NOT its rectangle/pins — only the two text strings render. Setting CurrentPartID:=1/DisplayMode:=0 on the copy did not fix it. The primitives inside the component group aren't being carried/registered onto the target sheet. Likely need to iterate the lib component's children and AddSchObject each onto the instance, or place via IntegratedLibraryManager rather than Replicate.
  2. STEP 3D body renders OFF-ORIGIN. add_body with ModelFactory_FromFilename attaches (returns ok), but in both the library 3D view and on the board the body sits at the model's native origin (a small sliver at the board corner), not over the pads — so the 3D board shows bare gold pads. Reposition the body to the footprint centroid after SetState_FromModel (set Body.ModelPosition/move), or bake the offset.

Also: PCB:SetCamera(To=RotateFit_*) is a no-op via RunProcess on AD 26.9.1 even with the window raised — the 3D view stays dead top-down. A real isometric needs either a focused numpad-key rotation (SendInput) or a different camera API; do not rely on SetCamera. Board substrate also renders white (no green plane) in the library/loose-PcbDoc 3D — a board shape + layer stack is needed for a proper 3D board.

Net for a demo today: symbol, footprint, library manager, schematic-with-part (text only), 2D board with the real footprint — all solid; 3D-with-body and schematic-graphics need the two fixes above.

6i. Demo fixes (2026-08-30) — schematic FIXED, one important bridge bug FIXED, 3D body still open

FIXED — place_on_sch now renders the full symbol. ISch_Component.Replicate does NOT re-parent a library part's pins/rectangle onto a normal sheet (only the designator/comment text placed). The working path: build a fresh SchServer.SchObjectFactory(eSchComponent), iterate the lib component's children (ePin/eRectangle/eLine/…), child.Replicate each, set OwnerPartId:=1/OwnerPartDisplayMode:=0, Inst.AddSchObject(child), then RegisterSchObjectInContainer + SendMessage. Proven on Ray's CH340G — 17 primitives (rect + 16 pins) render with all pin names on CH340G_Final.SchDoc. (Setting OwnerPartDisplayMode alone on the ORIGINAL symbol did NOT fix Replicate — the copy-primitives approach is required.)

FIXED — agent_start silently ran STALE code. The DelphiScript loop lives inside Altium; bridge_kill restarts the ab PROCESS but not the loop, and start() early-returned alreadyRunning when it saw the old loop's heartbeat — so redeployed agent code never loaded (every "fix" this session needed a manual agent_stop first). Fix: the agent stamps its generation (AGENT_GEN = template digest) into hb.txt; start() compares the resident gen to the current template digest and auto-stops+restarts a stale loop. ⚠ TWO traps hit while adding it: (1) _agent_base() must hash the RAW template, NOT rendered_agent_source() — the latter recursed and crashed the whole bridge; (2) editing agent.py/server.py needs a bridge_kill to reload the PROCESS, separate from restarting the in-Altium loop.

STILL OPEN — 3D component body does not render over the pads in the PCB Library 3D view. add_body attaches (STEP via ModelFactory_FromFilename, or an extruded contour) and returns ok, but neither shows as a solid over the pads in the footprint 3D editor — both appear as a small sliver in a corner. A post-add MoveByXY(BoundingRectangle…) centering attempt was WRONG (moved it further off) and has been reverted. Likely causes to chase next: STEP unit/scale on import; the PCB Library editor's "Display 3D Bodies" option; and bodies render reliably only on a real board (place the footprint on a PcbDoc WITH a layer stack + board shape, then view 3D) — the loose-PcbDoc/library 3D view is the weak path. Also PCB:SetCamera(To=RotateFit_*) is a no-op via RunProcess, so the view stays top-down regardless — a real isometric needs SendInput numpad keys to the focused 3D window. The Altium main window also self-MINIMIZED during the 3D switches; desktop_set_window_state {state:restore} fixes the sliver captures.

6j. 3D BODY FIXED end-to-end (2026-08-30) — five root causes, all measured

The chip now renders in 3D on a real board (black body + 16 gold pads on the green PCB, CH340G_Board14.PcbDoc). It took FIVE stacked fixes, each verified by data (altium_pcb_report — a new diagnostic verb that dumps per-component pads/bodies with layer + bbox in mils, plus a board object census by ObjectId):

  1. A primitive with no Layer is silently dropped. add_body never set Body.Layer; pads always set theirs. Fix: Body.Layer := eMechanical1 (+ enable LayerStack.LayerObject[eMechanical1].MechanicalLayerEnabled and LayerIsDisplayed) — and Body.BodyOpacity3D := 1.0.
  2. The stored lib component is NOT the editing surface. Fp.AddPCBObject(Body) counted 1 body immediately but the body was GONE after save+reopen — the editor canvas copy overwrites the stored component. Fix: PLib.CurrentComponent := Fp FIRST, then add to the CANVAS (PLib.Board.AddPCBObject(Body)); the editor persists the canvas into the component on save. (bodiesNow=1 AND survives reopen: libBodies=1.)
  3. Group Replicate of an IPCB_LibComponent DISSOLVES on a board — census showed 16 free pads, no eComponentObject, no body. Fix (same cure as the schematic): fresh PCBObjectFactory(eComponentObject), copy each child with primitive-level Replicate + Inst.AddPCBObject, and — critical — fill the group BEFORE Board.AddPCBObject(Inst): children added to an already-registered component exist in data but never draw, whatever address they're robot-registered to.
  4. The lib stores pads around the canvas origin (~50000,50000 mil) but an authored body contour around 0,0 — one group move can't land both (body hit the target, pads landed 50000 mil away). Fix: normalize per cluster — compute the copied PAD bbox center and MoveByXY pads (and non-text children) by target−center; move each BODY to the target by its own bbox center.
  5. Assigning Inst.x/y AFTER moving children re-translates the whole group (everything landed at 8000,6000). Anchor Inst.x/y := target BEFORE the normalization. Also hide NameOn/CommentOn (the factory "Designator1" text is huge and hijacks Zoom All; hidden texts still bloat the bbox — frame with Board.GraphicalView_ZoomOnRect(...) via eval instead of trusting Zoom All).

More traps measured on the way: GetCurrentPCBBoard answers with the LIBRARY's internal board when a PcbLib is focused (a board-reuse path placed into the lib canvas and renamed the lib doc — always create the PcbDoc and only then read GetCurrentPCBBoard); GetCurrentPCBLibrary answers even when a board is focused (pcb_report needs want:"board"); a nil BoardOutline.BoundingRectangle, SetFileName on an unassigned doc, and GroupIterator_Create on IPCB_Board all HALT the engine as "Undeclared identifier"; IPCB_Component.Name.Text := halts too (use NameOn/MoveToXY on Inst.Name). GroupIterators need AddFilter_LayerSet(AllLayers) or mech-layer children are invisible to them. WriteAtomic stage breadcrumbs into the workspace (agent/stage.txt) pinpoint hangs.

Still open (minor): the PCB Library editor 3D view does not render bodies (the board 3D does — use show_3d_chip for selection, the placed-board flow for true 3D); PCB:SetCamera remains a no-op so views are framed with GraphicalView_ZoomOnRect; the schematic place_on_sch fix (§6i) still stands. Cleanup owed: the fix session littered CH340G_v2..v8 libs and CH340G_Board..Board15 docs in Downloads and many open tabs.

Progress surface ruling (John, 2026-08-30)

Long-job progress goes on the Windows taskbar icon ONLY (desktop_taskbar progress on Altium's button — handlers/taskbar.py already paces it per verb). NOT ab progress toasts: "when i say progress bar i just mean in the windows taskbar icons." Toasts remain the nuclear only-a-human-can-act channel.

Ralph rule for generated CAD (John, 2026-08-30)

Frame-check EVERY generated look/variant, not a sample — the shipped symbol had colliding 4-side names, visible param texts (SetSchParam defaulted IsHidden False; now True), and std==2col, all invisible in a 3-of-10 spot check. symbol_modes.py now sizes bodies from measured name lengths (55 mil/char); ralph the full matrix before reporting.

6k. LAN7800 creative import (2026-08-30) — variants, display modes, footprint models, board proof

New verbs (all agent-lane, all measured): altium_build_footprint {libraryPath, spec} (spec from tools/kicad2altium.py: pads incl. duplicate-designator through-hole vias, roundrect %, tracks/arcs/filled discs on named mech layers), altium_build_symbol_modes {libraryPath, spec} (ONE component, N display modes from tools/symbol_modes.py), altium_symbol_mode {mode} (editor-driven: Sch:ToggleComponentDisplayMode Action=Mode0/Next — Comp.DisplayMode changes data only), altium_add_models {models[], params, comment, description} (N PCBLIB implementations = Altium footprint variants; the user picks the current footprint on the placed part), altium_normalize_bodies, altium_test_board {footprintName, wMm, hMm, assigns, stubs}, altium_board_shape {x0Mm,y0Mm,wMm,hMm} (process PCB:PlaceBoardOutline Mode=BOARDOUTLINE_FROM_SEL_PRIMS on 4 temp selected tracks — BoardOutline.PointCount/Segments is a silent no-op, .Clear halts), altium_swap_footprint {footprintName, libraryPath, assigns} (remove + per-primitive re-place at the same x/y, nets re-assigned by designator — tracks untouched, so wiring survives), altium_pcb_report gained padsBboxMil, nets, tracks.

Dialog pre-emption (John: a dismissed dialog is a defect). The DelphiScript debugger breaks on ANY raised exception even inside Try/Except — a raise IS an Error modal. ReadAll now probes with FileOpen (returns -1, never raises) before LoadFromFile. A halted command leaves PCBServer.PreProcess open = "a command is currently active" → save draws the save-a-copy Warning: TxClear (unbalanced PostProcess, harmless) runs at agent start, before every save, and after every recover. Undeclared identifiers hit this session: BottomXSize/Mid* pad sizes, IPCB_BoardOutline.Clear, eProcessFreePrimitives, IPCB_Board.GroupIterator_Create.

Library canvas offset. Bodies added through the PcbLib canvas land 50000 mil from the pads in the stored frame; the editor then paints an EMPTY canvas (Zoom All frames 50 inches; the CH340G "orange sliver" was this). normalize_bodies centres each body on its pad cluster; add_body now realigns automatically. GroupIterators need AddFilter_LayerSet(AllLayers) or mech-layer children are invisible.

Frames under a locked session. GDI editors (schematic, PcbLib 2D) still capture; the DirectX PCB canvas paints BLACK while the Windows session is locked (desktop_get_foreground = 0, desktop_list_windows = 0, full-screen capture white). Check the session before trusting a board frame.

7. Sources

Altium docs: scripting overview/setup, running scripts, DelphiScript, Altium SDK overview, Altium 365 API quick start + API page, embeddable viewer, licensing, sign-in. Vendor CI article (X2 -R recipe). Community: salitronic/eda-agent, coffeenmusic/altium-mcp, ee-in-a-box/altium-copilot, embedded-society/altium-designer-mcp, 10xgenomicsEng/altium-mcp-unified, oharown/mcp-codex-altium-designer, Altium-Designer-addons/scripts-libraries, KiCad Altium importer docs, vadmium/python-altium, thesourcerer8/altium2kicad.