Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
backendNoSelects the backend, passed as --backend altium (the default), --backend kicad, or --backend easyeda.altium
no-dashboardNoDisables the dashboard, passed as the flag --no-dashboard.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
app_update_from_librariesA

Drive Tools > Update From Libraries, ECO included.

THIS PRESSES BUTTONS IN THE GUI. Update From Libraries has no API: Altium documents it as a dialog, there is no process that launches it, and the operation ends in an Engineering Change Order that only a click can execute. So this matches windows belonging to the Altium process and presses named buttons on them.

WHAT IT WILL DO AT THE END. The last steps press Validate Changes and then EXECUTE CHANGES on the ECO. That applies every pending change to the design and is not undoable through this channel. Take app_checkpoint first.

WHAT IT CANNOT TELL YOU. While the wizard is open Altium's script engine is blocked, so the bridge cannot be asked anything, and an ECO's pending changes sit in a grid that exposes no text to Windows. The reply records the windows and buttons seen; it does NOT contain the change list. Read the Messages panel afterwards for that.

NOTHING IS SCRIPTED. There is no list of expected dialogs. Each one that appears is read, classified, and answered by the ROLE of the buttons it offers, then the screen is read again. That is deliberate: a scripted version failed on live Altium because the editor legitimately shows dialogs no list contained, and waited for a change order that was never coming while an unlisted modal blocked the session.

It STOPS rather than guessing when a dialog reports an error, asks a question it was not sent to answer, or offers no button whose meaning is recognised.

RUN IT DRY FIRST, WHICH IS THE DEFAULT. A dry run reads the first dialog, reports what it is and what it would press, and presses nothing.

LAUNCHING. already_open (the default) attaches to a wizard you have opened yourself, which avoids depending on a menu path nobody has verified. menu asks the bridge to fire the menu first; that request will not return until the wizard closes, which is expected and is why it is issued without waiting.

Args: target: schematic for Tools > Update From Libraries, or pcb for Tools > Update From PCB Libraries. launch: already_open or menu. dry_run: report without pressing. Default true. confirm_execute_eco: must be true for a real run, and it is named for the specific consequence rather than being a general "confirm", because the irreversible part is the ECO and not the wizard. answer_confirmations: answer routine "carry on?" prompts. Altium asks "Continue and create ECO?" before every change order, so with this off the run stops there and can never finish. A confirmation mentioning anything irreversible (delete, overwrite, cannot be undone) is still left alone whatever this is set to. first_step_timeout: seconds to wait for the FIRST dialog to appear. Raise it when launching by menu on a large project.

Returns: Dict with ok, dry_run, committed, stopped_for_a_human, finished explaining how the run ended, and steps: for every dialog, what it was, what it said, what it offered, what was pressed and WHY.

app_set_dialog_controlA

Read or CHANGE a control inside whatever dialog is open.

The other half of driving a dialog. Buttons are pressed by the dialog driver; this is for the checkboxes, radios and fields that decide what those buttons will DO.

FIND BY LABEL, not position. "Include Variants". Positions move between versions and contexts, captions do not.

EVERY CHANGE IS VERIFIED. The new state is read back, and a click that silently did nothing is reported as a failure rather than as success. That matters here more than anywhere: a caller that believes an option was applied will go on to press something irreversible.

IT REFUSES RATHER THAN GUESSING when a control is DISABLED (Altium greys options that do not apply, and clicking one is at best a no-op), and when asked to switch a radio button OFF, which is not a thing a radio does.

SETTING IS NOT A TOGGLE. Asking for a state the control is already in reports changed: false and clicks nothing, so calling twice is safe.

Args: control_label: the control's visible label. checked: desired state for a checkbox. text: desired contents for an edit field. select_row: for a list, grid or tree, the row to select by name. Rows are matched by name for the same reason menu items are: positions shift and an index would pick the wrong one the first time the contents changed. dialog_title: substring to pick one dialog when several are open. Empty uses the first. list_only: change nothing, just report every control in the dialog with its role, state and whether it is enabled.

Returns: For a read, the control list. For a change, ok, changed and the state read back afterwards.

app_click_menuA

Invoke ANY Altium menu command by path, by driving the menu.

Use this when app_run_menu will not do, which is more often than it looks: that tool reports success for commands it never invokes, because the process ids it dispatches cannot report failure and some of them are not real (task #83). This one clicks the actual menu, so a command either runs or the reply says why not.

PATH BY NAME. "Design|Rules...", "Tools|Update From Libraries...". Accelerator markers and a trailing ellipsis are ignored, so "Design|Rules" works too. Nested submenus work by adding levels. Names are used rather than positions because entries move with context and separators are invisible.

IT NEEDS THE FOREGROUND. Altium has no Win32 menu and its DevExpress bars expose no working accessible action, so the only thing that opens them is real input: this brings Altium to the front and briefly moves the pointer, putting it back afterwards. Every other press in this package is addressed to a window handle and depends on none of that, which is why this one is gated behind may_steal_focus and never happens by default.

THE MENU BAR IS PER EDITOR. With a board focused you get the PCB menus, with a schematic the schematic ones. Focus the matching document first, or the reply will tell you the item was not there and list what was.

WHAT HAPPENS NEXT IS NOT THIS TOOL'S JOB. Many commands open a dialog. Read it with app_list_open_dialogs; the dialog drivers handle the rest.

Args: menu_path: pipe-separated path, top level first. may_steal_focus: required to actually click anything. list_only: do not invoke. Reports the top-level menus currently on screen, which is the way to find out what this editor context offers. expect_dialog: part of the title of the dialog the command should open. Then ok means that dialog appeared; any other dialog, or none, fails and names what did open.

Returns: Dict with ok and opened (titles of dialogs that appeared); on failure reason and offered listing what that level actually contained. A path that leaves the menu open (it ended at a submenu, or the click did not land) is a failure: no command ran. A level that reads empty, or without the entry asked for, is walked once more before failing (retried); first_read then says what the first read found, and both reads are in reason when both fail.

app_list_open_dialogsA

What Altium has on screen, what it says, and if it is stuck.

THE TOOL TO REACH FOR WHEN THE BRIDGE GOES QUIET. A silent bridge looks identical whether Altium is showing a modal, busy compiling, or dead. This answers it directly: blocked is read from the main window being disabled, which is what Windows does while a modal is up, and it works precisely when no other tool can because it does not use the bridge at all. It reads the Win32 process, so a blocked Altium answers it normally.

blocked: false ONLY MEANS THE MAIN WINDOW IS NOT DISABLED. It does not mean the UI is alive. After Altium's UI crashed with the process still running, this was reported reading a live pid, blocked false and no dialogs. When the bridge is silent and this says not blocked, look at the screen before calling it a livelock.

FOR EACH DIALOG you get the caption, the message text, every button with its enabled state, and a kind:

  • error / warning / confirm -- something went wrong or a decision is being asked. These set needs_a_human.

  • nothing_to_do -- the operation completed with no work, for instance "Comparator Results (No Differences)" when the schematic and board already agree. A success, not a failure.

  • engineering_change_order -- an ECO awaiting Validate and Execute.

  • wizard / progress / unknown.

WHAT IT STILL CANNOT READ. Grids and owner-drawn lists hold their content internally and expose no window text, so an ECO's pending-change list is not readable. That is reported per dialog as has_unreadable_content rather than being passed off as a dialog with nothing in it. Read the Messages panel for the change list.

Reads only. Presses nothing.

Returns: Dict with blocked, dialog_count, dialogs, needs_a_human, and a one-sentence summary.

app_press_dialog_buttonA

Press ONE named button in an open Altium dialog.

The missing primitive. Dialogs could be listed, read and their checkboxes set, but the only code that pressed anything was buried inside a single task-specific driver, so answering an arbitrary dialog meant writing a script.

BY CAPTION, not position. Accelerator ampersands and a trailing ellipsis are ignored, so "Report Changes" finds "&Report Changes...".

IT WORKS WHILE ALTIUM IS BLOCKED, because it addresses the button's window handle over Win32 and never touches the bridge. That is the whole point: when a modal has the scripting engine stuck, this is what gets it unstuck.

IRREVERSIBLE PRESSES ARE GATED. A caption that commits a change order or applies a wizard is refused unless allow_irreversible is set, so a driver walking an unfamiliar dialog cannot execute one by reflex.

For a whole sequence rather than one press, use app_drive_dialogs, which decides each step from what is on screen instead of from a script.

Args: button_caption: the visible caption. dialog_title: substring picking one dialog when several are open. Empty uses the first. allow_irreversible: permit a committing press.

Returns: ok, the dialog and button actually matched, and whether the dialog closed afterwards.

app_drive_dialogsA

Answer dialogs reactively until none is left.

The reactive driver, which existed but was reachable only through the one wizard tool that used it. It reads whatever is on screen, classifies it, decides a press from that, and repeats. There is no scripted sequence, so a dialog appearing in a different order, or not at all, is handled rather than breaking a plan.

IT STOPS RATHER THAN GUESSING on a dialog it cannot classify, and on anything that needs a human. dry_run is the DEFAULT: it reports every decision and presses nothing, which is how to see what a sequence would do before letting it.

THE TWO GATES ARE SEPARATE ON PURPOSE. allow_confirm answers a yes/no question that moves an operation along; allow_commit permits the press that changes the design. Named for their consequences, because a caller reaching for one rarely means the other.

Args: intent: "proceed" to carry the operation forward, or "cancel" to back out of whatever is open. allow_commit: permit the design-changing press. allow_confirm: permit answering a confirmation. dry_run: decide and report, press nothing. Default True. wait_first: seconds to wait for the first dialog, since a caller fires a command and then drives. budget: seconds before giving up on a wedged editor.

Returns: Every observation and decision, plus committed, stopped_for_a_human and why it finished.

app_run_ui_commandA

Invoke a menu command AND answer the dialogs it raises.

The composite, and usually the one to reach for. Firing a menu command that opens a modal blocks Altium's scripting engine, so the two halves cannot be separate bridge calls: the second would never be answered. Both halves here are Win32, so they run in one call and the modal is no obstacle.

Doing this by hand meant arming a watcher process before firing the command, which is not something a caller should have to assemble.

THE GATES ARE THE DIALOG DRIVER'S. allow_confirm defaults ON because a command invoked deliberately usually needs its "yes, go ahead" answered, while allow_commit defaults OFF, so a change order is presented rather than executed. dry_run defaults OFF here, unlike app_drive_dialogs: naming a command to run and having nothing happen is not what the caller asked for.

Args: menu_path: pipe-separated, for example "Tools|Annotation|Force Annotate All Schematics...". Discover paths with app_click_menu(list_only=True). allow_commit: permit a design-changing press. allow_confirm: permit answering confirmations. dry_run: invoke the command but press nothing. may_steal_focus: opening a menu needs real input. wait_first: seconds to wait for the first dialog. budget: seconds before giving up. expect_dialog: part of the title of the dialog the command should open. When a different dialog (or none) appears, the reply fails at the menu stage and nothing is driven.

Returns: ok, the menu result, and the full dialogs record. ok is true only when the menu click AND the drive both succeeded, so a command that opened something nobody could answer does not read as a success.

app_list_panelsA

Altium's dockable panels that are currently on screen.

PANELS ARE NOT DIALOGS and were unreachable until now. A dialog is a top-level window; a docked panel is a child of the main frame, so app_list_open_dialogs never saw one. Projects, Navigator, PCB, Properties and Messages all live here.

Args: offered_too: also read what View > Panels lists, which is what COULD be opened rather than what is open. That needs a real menu click and steals focus, so it is off by default.

Returns: {"ok": true, "panels": [{name, rect, button_hwnd, content_count}], "count": N}, plus offered when asked for. A panel is found by the tab it carries, so an empty list comes with a note: it has been reported empty on AD 25 while Properties was open.

app_open_panelA

Open a dockable panel by name, and confirm it appeared.

Goes through View > Panels, because Altium exposes no scripted way to show a panel and application.execute_menu reports success while invoking nothing. So this STEALS FOCUS, like every menu click. Reading a panel afterwards does not.

A panel that is already open is reported as such and NOT clicked. Those menu entries are toggles, so clicking one you already have would close it, and asking for a panel is not a request to lose it. When the panel cannot be seen after the click (it may have been open with no tab the scan reads), the entry is clicked once more so the panel is left as it was, and the reply says so with toggled_back. Calling again does not help in that case.

Args: name: the panel's name as View > Panels spells it, e.g. "Projects", "Messages", "PCB", "Properties". settle: how long to allow for it to appear.

Returns: {"ok": true, "name": ..., "opened": bool, "rect": ...}. opened is false when it was already there.

app_read_panelA

Read what is inside an open panel.

Takes no focus and moves nothing, so it is safe while a user is working. The panel must already be open: use app_open_panel first, or app_list_panels to see what is there.

Reads the WHOLE panel. It is not fast: Altium materialises its accessible tree lazily, so the first touch of a node costs about 116 ms and 162 rows is roughly thirty seconds. Slow and complete beats fast and partial.

Returns: {"ok": true, "name": ..., "row_total": N, "grids": [{control, hwnd, row_count, rows}]}. Grids are reported separately because a panel holds more than one and they mean different things.

app_set_dropdownA

Choose an option in a combo box or drop list, and verify it.

THESE WERE READABLE AND UNSETTABLE. combobox and droplist have been reported by app_list_open_dialogs from the start, so a caller could see a combo and its current value with no way to change it, which is the same shape as a property that reads and will not write.

Three routes are tried, cheapest first: the option's own default action, an accessible selection, then a real click on it. The value is READ BACK afterwards, so a route that runs and changes nothing is reported rather than believed.

Args: control_label: the combo's label. option: the option to choose, matched by name. dialog_title: which dialog, when several are open. list_only: report the options and change nothing.

Returns: {"ok": true, "value": ..., "how": ..., "was": ...}, or ok: false with offered listing what is there.

app_expand_tree_nodeA

Open or close a node in a tree, and verify the state changed.

Trees were readable and could not be OPENED, so anything nested inside one did not exist as far as a caller was concerned.

Args: control_label: the tree's label. node_name: the node to open, matched by name. expand: True to open, False to close. dialog_title: which dialog, when several are open.

Returns: {"ok": true, "node": ..., "expanded": bool, "changed": bool}. changed is false when it was already in that state, which is a success rather than a no-op to worry about.

app_set_grid_cellA

Type into one cell of a grid, and read it back.

ALTIUM'S RULE AND PROPERTY EDITORS TAKE THEIR INPUT THIS WAY. Selecting a row was possible before; changing anything in it was not.

A grid cell is not an edit control until it is being edited, so the cell is activated first and the text goes to whatever editor the grid puts there. The value is then read back off the CELL, because the editor disappears when the edit commits.

Args: control_label: the grid's label. row: the row, matched by name. column: the column name, or a numeric index. These grids leave many cells unnamed, and an index is then the only handle there is. text: what to type. dialog_title: which dialog, when several are open. list_only: report the row's cells and change nothing. Worth doing first, since it shows which columns have names.

Returns: {"ok": true, "row": ..., "column": ..., "value": ...}, or ok: false when the cell reads back differently, which happens on read-only columns and on ones that validate and revert.

app_send_keysA

Send a key sequence to whatever currently has focus.

THE THINGS THAT TAKE NEITHER A CONTROL NOR A MENU: a canvas shortcut, a grid that commits on Enter, a dialog answered with Escape, a chord like Ctrl+Shift+D. Every other tool here addresses a control; this addresses the keyboard.

Space separated, each item a key or a '+' chord:

"escape"              one key
"ctrl+s"              a chord
"tab tab enter"       three in order

Modifiers: ctrl, alt, shift. Keys: enter, escape, tab, space, backspace, delete, the arrows, home, end, pageup, pagedown and F1 to F12. An unknown name is refused and the known ones listed, rather than sent as nothing.

THIS GOES WHEREVER FOCUS IS. Establish focus first, with app_click_menu or app_open_panel, and check the result afterwards: a keystroke has no read-back, so the reply says what was sent and never that it worked.

app_type_textA

Type literal text into whatever has focus.

For a field that HAS a handle, app_set_dialog_control is better: it addresses the control by name and reads the value back. This is for the places with no handle to address, where typing is the only route in.

Carries punctuation and case, since it sends characters rather than virtual keys. No read-back, for the same reason as app_send_keys.

app_context_menuA

Right click at a point: read the context menu, or pick from it.

CONTEXT MENUS WERE ENTIRELY UNREACHABLE, and Altium puts a great deal in them that no top-level menu duplicates: what you can do to a component, a net, a polygon, a row in a panel. Driving the menu bar while ignoring these leaves most of the editor shut.

With no item this OPENS the menu, reads what it offers and CLOSES it again, invoking nothing. Leaving a popup dropped blocks the next operation and looks to a user like a hang.

Args: x, y: screen coordinates to right click. Panel and control rectangles from app_list_panels and app_set_dialog_control(list_only=true) give you these. item: the entry to choose. Nested entries take a '|' path, matched by name at each level like the menu bar.

Returns: Reading: {"ok": true, "items": [...], "count": N}. Choosing: {"ok": true, "item": ...}, or ok: false with offered listing what was actually there.

app_toolbarA

List toolbar buttons, or press one by name.

Toolbars are the other half of the bar framework the menus use and were not addressed at all. Many carry a command with no menu equivalent, or reach in one click what the menu takes three to.

With no name this lists every button it can see with its rectangle. With one, it presses that button and reports whether a menu dropped as a result, since many toolbar buttons open one rather than acting.

app_dialog_tabA

List a dialog's tab pages, or switch to one.

PROPERTY DIALOGS HIDE MOST OF THEMSELVES BEHIND TABS. Nothing could switch one, so app_set_dialog_control only ever saw the front page and reported it as the whole dialog: a control on any other page read as a control the dialog does not have.

With no name this lists the pages and which is selected.

app_scroll_controlA

Scroll a list, grid or tree so more of it comes into view.

A LONG LIST HIDES ITS OWN CONTENTS. A virtualised DevExpress grid exposes roughly what is on screen, so a row further down did not exist as far as a caller was concerned, and the reply gave no hint that anything was missing.

Positive scrolls down, negative up. The reply reports whether the rows actually changed: moved: false at the end of a list is a real answer rather than a failure.

app_activate_rowB

Invoke a row, rather than merely selecting it.

app_set_dialog_control(select_row=...) highlights a row. This ACTS on it, which in a panel is the difference between pointing at a document and opening it.

A click has no read-back, so the reply reports what was done and not what it caused.

app_screenshotA

Capture Altium, or one dialog, to a PNG.

THE AGENT COULD NOT SEE THE UI. ui/ocr.py has been able to capture and read a window since it was written and none of it was exposed, so every judgement about what was on screen came from an accessible tree that omits anything drawn rather than placed. A picture settles what a tree cannot: which page is in front, whether a control is greyed, what a rendered dialog actually says.

Args: path: where to write the PNG. Defaults to the workspace directory, so the file is somewhere findable. dialog_title: capture that dialog rather than the main window.

Returns: {"ok": true, "path": ..., "hwnd": ...}. Read the file to look at it.

app_canvasA

Click or drag on the editor canvas, at screen coordinates.

THE CANVAS HAS NO NAMED CONTROLS, so every other tool here could not touch it. Placing, moving, rubber-band selecting and routing are pointer gestures and there was no way to make one.

With to_x/to_y this DRAGS, moving in steps rather than jumping, because a jump reads as a click at the destination. Without them it clicks.

NO READ-BACK IS POSSIBLE. The reply says what gesture was made and never what it did. Confirm with a bridge read, which can see the document, rather than trusting this.

app_wait_for_dialogB

Block until a dialog appears, and report which one.

A COMMAND THAT RAISES A DIALOG RETURNS BEFORE THE DIALOG EXISTS. A caller that acted and then looked found nothing and concluded none was coming; the alternative was a fixed sleep long enough to be safe, which is the pattern this layer spent the day removing everywhere else.

app_ui_snapshotA

Everything on screen, in one call: menus, panels, dialogs, toolbars.

ORIENTATION IS THE EXPENSIVE PART. Finding out what is available took four calls and knowing which four to make, and a caller who guessed wrong concluded a capability was missing. This is the UI equivalent of app_context: one call that answers "what am I looking at".

Reads only. Takes no focus, moves no pointer, changes nothing, so it is safe while somebody is working. The menu BAR is listed because reading it needs no click; menu contents are not, because opening one does.

app_read_windowA

Read a window through UI Automation, which sees what MSAA cannot.

THE LAYER THAT WAS MISSING. This project read Altium three ways: window text, which returns nothing for anything without a handle; MSAA, which handles the VCL dialogs well and the WPF ones not at all; and OCR on the pixels. UIA sits between the second and the third.

It matters most for the case that forced OCR into existence. Altium's newer WPF dialogs have buttons that are NOT child windows, so EnumChildWindows finds nothing and MSAA has nothing to press. WPF is UIA-native, so those same buttons are ordinary elements here, with names, and they can be pressed properly with app_invoke_element.

WHAT IT STILL WILL NOT SEE. Delphi's TLabel owns no handle and publishes no accessible object, so its text exists only as pixels. Use app_screenshot there; that is what OCR is for and why it stays.

Returns: {"ok": true, "count": N, "elements": [{name, type, automation_id, enabled, rect}], "text": [...]}.

app_invoke_elementA

Press, set or expand an element through UI Automation patterns.

PATTERNS ARE NOT A SIMULATED CLICK. MSAA offers little beyond a default action, which is why the rest of this layer falls back to moving a real mouse. UIA exposes what a control actually implements: Invoke to press, Value to set text, ExpandCollapse to open a node. Those are supported operations, so they move no pointer, take no focus, and cannot land on whatever happens to be under the cursor.

That makes this the right tool for a WPF dialog, and for anything you want done while the user keeps working.

A CLIENT THAT DOES NOT LIST THIS TOOL can still reach it through the dispatcher: tool_invoke(name="app_invoke_element", arguments={"name": "OK", "dialog_title": "Silkscreen"}).

Args: name: the element's name or automation id. A Button of that name is preferred over a label carrying the same text. dialog_title: which dialog. Empty uses the frontmost open dialog, or the main window when no dialog is open. text: set this value instead of pressing. expand: "true" or "false" to open or close a node instead.

Returns: ok with what was done and, for a press in a dialog, dialog_closed; or a refusal naming which pattern the element lacks and its rectangle, so a real click remains possible as a fallback.

app_get_statusA

Check if Altium Designer is running and get status information.

Returns information about the Altium Designer process including:

  • Whether Altium is running

  • Process ID

  • Executable path

  • Whether the MCP bridge is attached

MORE THAN ONE ALTIUM. candidate_count and candidate_pids list every instance, and selected_by says how pid was chosen: the instance owning the bridge's status window, or the only one with a visible window. When neither decides it, pid is None, ambiguous is True and reason explains, and every UI tool refuses rather than read the wrong process's dialogs.

MORE THAN ONE eda-agent SERVER. other_servers lists the other servers recorded on this workspace (pid and start time), and warnings says so when there are any. A warning only: several sessions at once are allowed, and nothing is stopped.

Returns: Dictionary with status information

app_contextA

Where you are, in one call. Run this before anything else.

THE FOUR FACTS THAT EXPLAIN MOST "WHY DIDN'T THAT WORK". Each has cost a session on its own, and each was individually available and individually unasked for:

  • Which document is focused, and what kind it is. Nearly every tool acts on the focused document. A pcb_ tool aimed while a library is focused does not always fail; it can resolve some other open document and report success for work you did not ask for.

  • Whether the running script matches the one on disk. DelphiScript caches compiled units, so a deployed fix does nothing until the script project is reopened. Symptom: a fix that "did not work".

  • What is unsaved. An edit that is real in memory and absent from disk reads as a tool that silently did nothing.

  • Whether the bridge is answering at all, as opposed to Altium being up.

This composes existing reads rather than adding a bridge call, so it is cheap and cannot disagree with the tools it summarises.

WHAT IT DOES NOT TELL YOU: which library component is current. That is editor state with no read exposed, and the library authoring tools depend on it. If a lib_add_* call lands somewhere unexpected, set the target explicitly with lib_set_current_component rather than assuming.

Returns: {"backend": ..., "bridge_answering": bool, "script_version_match": bool, "active_document": {...}, "open_document_count": N, "unsaved": [paths], "warnings": [...], "next_step": ...}.

``warnings`` is empty when nothing needs attention.
``next_step`` is the single thing worth doing first, or empty.
app_attachA

Connect to a running Altium Designer instance.

This verifies Altium is running and the polling script is responding. The Altium_API.PrjScr script must be running (StartMCPServer) in Altium.

Returns: Dictionary with attachment status

app_save_allA

Flush every dirty Altium document to disk.

Mutation tools (pcb_place_tracks, move_component, modify_objects, ...) now mark documents as modified in-memory only. Changes stay fast because they skip per-operation disk writes. Call save_all at logical checkpoints, after a routing pass, before running DRC, or before closing, to persist everything.

Detach also triggers save_all automatically, so you don't need this as the very last step.

CHECK still_dirty, DO NOT ASSUME. Altium refuses to save while a command is active in the editor and asks a human whether to write a copy instead; DoFileSave does not raise when it declines. MEASURED: this returned saved:true while every single save was being refused. It now counts what is still modified afterwards, and saved is false when anything is. If that happens, run app_exit_active_command and retry.

Returns: Dict with saved, still_dirty, and a reason when documents remain unsaved.

app_save_documentA

Save ONE open document: file_path, or the focused one.

app_save_all writes every open document, so one that cannot be written, a sheet in a read-only folder say, stops a save you never asked for. This touches only the named document, never changes focus and never walks the workspace.

saved is measured from the file's age on disk: Altium does not report a declined save. was_modified says whether Altium thought there was anything to write, and it misses edits made through scripts: a board changed only by this server's tools read false and was saved all the same. A save Altium declines while a command is active reads saved false; run app_exit_active_command and retry.

Args: file_path: Absolute path of an open document. Empty saves the focused document.

Returns: file_path, saved, was_modified and a reason when it was not saved. NOT_OPEN when the document is not open, since only an open document can hold unsaved edits.

app_exit_active_commandA

Close a transaction a crashed handler left open in Altium.

THE TOOL FOR "A command is currently active and save cannot be completed at this time". While that state is set, EVERY save is refused and the editor asks whether to write a copy instead.

IT SURVIVES RESTARTING THE POLLING LOOP, because the state lives in Altium's PCB server rather than in the script, and Escape in the editor does not clear it either. MEASURED on 2026-08-25: saves were being refused on a brand-new library, and the state had been left hours earlier by lib_link_3d_model faulting between PreProcess and PostProcess on an undeclared identifier. It outlived several restarts.

Every PreProcess in this bridge is paired, including on its error paths, so this is not routine cleanup: it is recovery from a handler that DIED mid-transaction. AltiumScriptCentral ships the same one-line remedy as ExitActiveCommand.vbs.

Calling it when nothing is outstanding is harmless.

It saves nothing by itself. Run app_save_all afterwards and check still_dirty.

Returns: Dict reporting whether each server accepted the unwind, plus the count of still-modified documents before and after.

app_close_documentA

Close ONE open document, found by its full path, whatever has focus.

Unlike closing through the workspace manager, which acts on the FOCUSED object, this closes the named document itself. It refuses a document that reads modified, and says whether it is really gone afterwards. The modified read can miss edits held in the editor, so save anything you care about first; a close of an edited document may still raise Altium's save prompt.

Args: file_path: the document's full path, as app_list_documents reports it. discard_changes: close it even when it reads modified, and LOSE the unsaved edits: the modified flag is cleared first so no save prompt appears. For scratch copies and documents Altium changed by merely opening them, never for work.

Returns: {"closed": bool, "discarded": bool, "file_path": ...}; an error when the path is not open, or when the document reads modified and discard_changes is not set.

app_checkpointA

Snapshot the focused project so the session is revertible.

Copies the project's design files into a content-addressed store under the workspace (unchanged files are deduplicated, so repeat checkpoints are cheap). Take one before any risky autonomous edit; restore with app_restore_checkpoint. This is the undo the live bridge otherwise lacks.

Args: label: optional human note ("before routing pass"). save_first: flush dirty Altium docs to disk before snapshotting so the checkpoint reflects in-editor changes (default True).

Returns: The checkpoint manifest summary (id, created, file_count, ...). With save_first, also saved: True when the flush worked, False with save_error and a note when it did not. A checkpoint is still taken either way, but one taken after a failed save holds the on-disk state only, which is missing precisely the in-editor work you were protecting.

app_list_checkpointsA

List saved checkpoints for the current workspace, newest first.

app_restore_checkpointA

Restore the focused project's files from a checkpoint.

Overwrites design files with the snapshot contents. Close the project in Altium (or expect a reload prompt) before restoring, since Altium holds documents open. With prune_added=True this is a true revert: files created after the checkpoint are deleted; the default leaves newer files untouched.

Args: checkpoint_id: id from app_list_checkpoints. prune_added: delete files absent at checkpoint time (default False).

Returns: {restored, removed, missing_blobs}.

app_detachA

Stop the Altium MCP polling loop. CALL THIS WHEN YOU'RE FINISHED.

While the eda-agent MCP server is connected, a keep-alive thread pings Altium every 30 s, which keeps Altium's scripting engine held by the polling loop, Altium's own script-backed UI commands (some ribbon buttons, Parameter Manager actions, etc.) may be unresponsive until the loop is released.

Call this tool once you've finished your Altium work for the session. It flushes every dirty document via save_all, then sends application.stop_server so the DelphiScript loop exits cleanly within ~500 ms and stops the Python keep-alive. Altium becomes immediately fully responsive.

NOTE: After detach, the Altium script has fully stopped. To run more MCP tools later in the same Altium session the user must re-launch StartMCPServer via File -> Run Script. Don't detach until you're confident you're done.

Returns: Dictionary confirming detachment

app_pingA

Test if the Altium script is responding and report script version.

Verifies that:

  1. Altium Designer is running

  2. The Altium_API.PrjScr script is running (StartMCPServer)

  3. File-based communication is working

Also reads SCRIPT_VERSION from the .pas that Altium has compiled and compares it to the version in the bundled on-disk Main.pas. A mismatch means Altium is running a stale cached script: close the script project in the Projects panel and reopen it to recompile. Re-running StartMCPServer does not recompile, and a restart of Altium alone has not always been enough.

SCRIPT_VERSION only speaks for Main.pas. Each unit also reports a stamp that install-scripts writes from its content, and stale_units names any unit whose running compile differs from the files Altium loaded.

Returns: Dictionary with: - success: True if Altium responded - mcp_server_version: version of this eda-agent Python package (from eda_agent.__version__), identifies the MCP server process currently handling tool calls - altium_script_version: version the running script reports (empty string if the script is too old to report it) - bundled_script_version: version of the on-disk Main.pas - version_match: True if Altium matches bundled script version - altium_version: the Altium build itself, as Altium reports it. Reported, never acted on: nothing here refuses to run on an old build, because most of the toolset works on one. It is here because a bug report without it costs a round trip, and twice the answer changed the diagnosis. - message: human-readable status (flags stale cache if detected) - bundled_script_path: the Main.pas that bundled_script_version was read from, so a mismatch can be traced to a file - script_projects_open: the Altium_API.PrjScr paths Altium has open, as the running script reports them (None when it is too old to report them) - script_location_match: True when one of those is in the bundled or the install-scripts folder, False when none is (version_match then describes a file Altium did not load), None when unknown - unit_stamps: each unit's stamp as the running script reports it (None for a script too old to report them) - stale_units: units whose running compile differs from the files in stamps_compared_with; unstamped_units run from copies install-scripts did not stamp - other_servers: other eda-agent servers on this workspace, each with pid and start time - warnings: anything above worth acting on. Warnings only: nothing here refuses or stops anything.

app_get_reportA

Report the health of the MCP bridge, workspace, and Altium link.

One call to diagnose the plumbing without touching the design: package version, the script version on disk vs. what Altium has compiled, whether Altium is up, the live IPC round-trip time, the workspace location, and how many request/response/stop files are sitting in it (a backlog of stale request_*.json usually means a dead or wedged poller).

Returns: Dictionary with mcp_server_version, bundled_script_version, altium_running, altium_script_version, version_match, round_trip_ms (None if Altium is down), workspace_dir, workspace_exists, and a file_counts breakdown.

app_create_documentA

Create a new blank document of a given kind and save it to disk.

Wraps IClient.OpenNewDocument + DoFileSave. The new document is written to file_path and, by default, attached to the currently focused project. Use this to create a .PcbDoc before running update_pcb, to spin up a fresh .SchDoc, library, OutJob, etc.

Args: kind: Document kind, 'PCB', 'SCH', 'PCBLIB', 'SCHLIB', 'OUTPUTJOB', or any other kind Altium's server module registers under. file_path: Absolute path where the new document should live. Use Windows backslashes. name: Optional display name. Defaults to the filename. add_to_project: Attach the new file to the focused project. Default True. Set False to leave it as a free document.

The new document is focused before this returns, and focused says whether that took: the library and board tools act on the focused document, and a new one left behind the old meant the next call wrote into the old one. A document that could not be saved is an error (a missing folder is named), not a success with saved: false.

Returns: Dictionary with kind, file_path, saved, focused, added_to_project.

app_list_documentsA

List all documents known to the current Altium workspace.

Returns both project members and any free documents. Each entry carries a loaded flag that distinguishes "listed as project member on disk" from "actually resident in the editor". Project-scope queries (query_objects, batch_modify, ...) only iterate loaded sheets, if loaded is false for sheets you need to hit, call load_project_sheets first.

Returns: List of document information dictionaries containing: - file_name: Document file name - file_path: Full file path - document_kind: Type of document (SCH, PCB, etc.) - loaded: True if the doc is resident in the editor server. False means it's a project member on disk whose editor state hasn't been opened yet. - modified: True if the editor holds unsaved edits. A false is a floor rather than a clean bill, since the flag does not always propagate from ProcessControl. app_save_all measures file timestamps instead of trusting it.

app_diag_workspaceA

Diagnostic: enumerate workspace files via Altium's FindFiles helper.

Reports workspace_dir, the pattern used, match_count and the first 10 matching filenames. Used to validate the per-request file enumeration that the dispatcher relies on.

app_get_active_documentA

Get information about the currently active (focused) document.

Returns: Dictionary with active document information: - file_name: Document file name - file_path: Full file path - document_kind: Type of document (SchDoc, PcbDoc, etc.) - modified: True if the editor holds unsaved edits. A false is a floor rather than a clean bill, since the flag does not always propagate from ProcessControl. Returns empty dict if no document is active.

app_set_active_documentC

Set a specific document as the active (focused) document.

Args: file_path: Full path to the document to activate

Returns: Dictionary with result of the operation

app_get_versionA

Get the version of Altium Designer.

Uses Client.GetProductVersion internally. If that API is unavailable (older builds or restricted script context), the returned dictionary will omit "version" and include a "note" field instead.

Returns: Dictionary with product_name and either: - version: Full version string (when Client.GetProductVersion works) - note: Explanation when the version API is unavailable

app_get_preferencesA

Get key Altium Designer preferences.

Returns PCB preferences (snap grid, display unit) from the active board and schematic preferences (visible/snap grid) from the active schematic. Values are null if no PCB or schematic is currently open.

Returns: Dictionary with "pcb" and "schematic" sub-objects containing grid and unit settings

app_run_menuA

Dispatch a menu command by path. DISPATCH IS NOT EXECUTION.

This sends the command and cannot tell you it ran. Altium's RunProcess accepts an unknown process id in silence and reports nothing back, so a typo, a command unavailable in the current editor, and a command that worked are indistinguishable from here. MEASURED 2026-08-17: "Tools|Preferences" returned in 0.11s with success: true and no dialog ever appeared. That is why the reply says dispatched, never executed, and carries outcome_verified: false.

Use app_click_menu when you need the command to actually happen. It walks the real menu bar and clicks the item, so it fails loudly on a path that does not exist and reaches the many commands that have no process id at all.

These paths are mapped to internal processes:

  • "File|Save All"

  • "Tools|Design Rule Check"

  • "Tools|Electrical Rules Check"

  • "Project|Compile"

  • "Edit|Select All" / "Edit|Deselect All"

  • "View|Zoom Fit"

  • "Tools|Preferences"

  • "Tools|Extensions and Updates"

Anything else is REFUSED with UNMAPPED_MENU_PATH. The old fallback guessed a MenuID out of the pipe-separated path and was measured opening nothing while reporting success, so refusing and pointing at app_click_menu is strictly more useful than a guess that cannot work.

Args: menu_path: Menu path using pipe separators (e.g., "File|Save All")

Returns: Dictionary with dispatched, outcome_verified (always false), menu_path, and the process used. A reply the bridge could not parse is reported as a failure, not smoothed into a success.

app_set_intentA

Tell the dashboard what high-level task the agent is working on.

The text is written to workspace/intent.txt where the web dashboard polls it and shows a banner. Purely informational -- does not affect tool dispatch in any way. Call this once at the start of a long task ("reviewing buck-converter feedback divider", "auto-placing the analog front-end on sheet B") so the user watching the dashboard knows what's happening between IPC events. Pass an empty string to clear the banner.

Args: intent: Short human-readable description (one line, <=240 chars).

Returns: {"ok": true, "intent": "<truncated text>"}.

app_get_clipboardA

Get text content from the Windows clipboard.

Returns whatever text is currently on the clipboard, which can be useful for reading data copied from Altium dialogs or reports.

Returns: Dictionary with "text" containing the clipboard content. Returns empty string if clipboard is empty or non-text.

proj_createB

Create a new Altium project.

Args: project_path: Full path for the new project file (.PrjPcb, .PrjLib, etc.) project_type: Type of project to create: - "PCB": PCB project (.PrjPcb) - "IntegratedLibrary": Integrated library project - "ScriptProject": Script project

Returns: Dictionary with created project information

proj_openC

Open an existing Altium project.

Args: project_path: Full path to the project file

Returns: Dictionary with opened project information

proj_saveB

Save the current or specified project.

Args: project_path: Optional path to specific project. If None, saves active project.

Returns: Dictionary confirming save operation

proj_closeA

Close a project, saving its documents or discarding their edits.

save=True (default) writes this project's modified documents, then closes it. save=False closes it without saving.

WITHOUT SAVING, TWO LAYERS KEEP THE BRIDGE FROM WEDGING. A close with a modified member raises Altium's save prompt inside the polling loop, where the bridge cannot answer it. MEASURED on AD 26.10.1.6: after an edit IServerDocument.Modified reads False while the tab shows the sheet modified, so nothing gated on that read works.

First, the handler clears the modified flag on every loaded member without reading it. MEASURED on a local scratch project: the close then raised no prompt and the file on disk was byte-identical.

Second, if a prompt appears anyway, this answers it through UI Automation: "Save None", then "OK" once the dialog has retitled to "Confirm Not Saving", which is what shows the decisions changed. Pressing OK before that would save. The sequence was measured by hand; this tool running it has not run live.

ALTIUM CLOSES THE FOCUSED PROJECT. MEASURED: a close naming a project with no loaded document, while another project's board was focused, closed that other project first. So the handler makes the named project focused by showing one of its loaded documents, and refuses, changing nothing, when it cannot. Every attempt compares the open projects before and after: if anything else closed, it stops without retrying and returns those paths as closed_instead, with success false.

IT ANSWERS ONLY FOR THIS PROJECT. It records the project's member paths before closing and reads every path the prompt lists. If any listed document is outside the project it presses nothing and returns them, because answering would discard work in a project nobody named. It also presses nothing when membership cannot be established: no project_path, or a deployed script that reports member file names rather than paths.

A document shared with another open project is a member of both, so its unsaved edits are discarded here too. save=True prompts are never answered for you.

IF A PROMPT IS LEFT OPEN, answer it with app_invoke_element (Save None, then OK) or app_press_dialog_button. Both work while the bridge is blocked, because they go through Win32 rather than through the loop.

Args: project_path: project to close. Omit for the focused project; save=False then leaves any prompt for a human. save: write modified documents first (True), or discard their unsaved edits (False).

Returns: success and closed, checked by looking the project up again, and project_path. When a prompt was answered, prompts_answered and discarded, the paths it listed. When one was left, prompt and reason: the bridge stays blocked until it is answered. Without a prompt, discarded lists the loaded members closed without saving and attempts how many closes it took. The modified flag cannot be read reliably, so a path in discarded is not proof that document had edits.

proj_list_documentsA

List all documents in a project.

Args: project_path: Optional path to specific project. If None, uses active project.

Returns: List of document information dictionaries containing: - file_name: Document file name - file_path: Full file path - document_kind: Type of document

proj_add_documentB

Add an existing document to a project.

Args: document_path: Full path to the document to add project_path: Optional project path. If None, uses active project.

Returns: Dictionary confirming the operation

proj_remove_documentA

Remove a document from a project.

Note: This only removes the document from the project, it doesn't delete the file.

Args: document_path: Full path to the document to remove project_path: Optional project path. If None, uses active project.

Returns: Dictionary confirming the operation

proj_get_parametersA

Get all parameters defined at the project level.

Args: project_path: Optional project path. If None, uses active project.

Returns: List of parameter dictionaries with name and value

proj_set_parameterC

Set a project-level parameter.

Args: name: Parameter name value: Parameter value project_path: Optional project path. If None, uses active project.

Returns: Dictionary confirming the operation

proj_push_parametersA

Copy all project-level parameters onto each loaded schematic sheet.

Every project parameter is written as a document (sheet) parameter on each LOADED schematic in the focused project, so title blocks can reference project-wide values. Unloaded sheets are skipped -- call proj_load_sheets first to include every sheet.

Returns: Dict with param_count, sheets_updated, sheets_skipped_not_loaded, and params_pushed.

proj_get_netsA

Get net-to-pin connectivity from the compiled project netlist.

CRITICAL for bulk queries, if you need connectivity for MORE THAN ONE component or net, do NOT loop this tool. Call it ONCE with no filters (component="", net_name="", raise limit if needed) to pull the entire pin-net table in a single round-trip, then slice the result locally. Each filtered call is ~700 ms and compiles the project.

Compiles the project and returns pin-level net assignments.

Args: component: Filter by component designator. Empty = all. net_name: Filter by net name. Empty = all. project_path: Optional project path. If None, uses active. limit: Max pin records (default 500). Raise for big boards. force_recompile: Save all dirty docs, invalidate the SmartCompile cache, recompile. Costs one extra compile (~5-10 s on real designs). Use when you need a guaranteed-fresh netlist (e.g., after the user edited schematics in the UI). Pair with proj_get_compile_freshness to confirm no docs are dirty.

Returns: Dict with "pins", "count", "limit", "limit_hit" (true when the list stopped at limit with pins left over) and "skipped_nil" (entries Altium returned empty).

Examples: # PREFERRED, one unfiltered call, then filter locally: all_pins = proj_get_nets(limit=10000)["pins"] u1_pins = [p for p in all_pins if p["component"] == "U1"]

# Guaranteed-fresh read after user edits:
fresh = proj_get_nets(force_recompile=True, limit=10000)
proj_export_netlistA

Write the compiled connectivity to a flat netlist file.

Pulls the full pin-net table once (the same data as get_nets) and writes it as a standalone netlist that other tools can import. Two formats:

  • "pads": PADS-PCB ASCII, NET section with one SIGNAL block per net listing its component.pin nodes. Widely importable.

  • "tabular": one "component,pin,pin_name,net" CSV row per node, for quick diffing or spreadsheet review.

Single-pin and unnamed nets are included so the file is a faithful dump, not a routed-only view.

Args: output_path: Destination file. Defaults to workspace/netlist. if omitted. net_format: "pads" (default) or "tabular". project_path: Optional project path; uses active if omitted.

Returns: Dictionary with output_path, net_format, net_count, node_count.

proj_compileA

Compile a project to check for errors.

This runs the project compilation which validates connectivity and checks for design errors.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with compilation results including any errors/warnings

proj_load_sheetsA

Load every schematic sheet of a project into the Altium editor.

Project-scope operations (query_objects, batch_modify, etc. with scope="project") only iterate sheets already resident in SchServer. A sheet listed as a project member via get_open_documents may still show loaded=false, meaning Altium hasn't opened its editor state. Call this tool first to force every sheet to load as a proper project member (no free documents).

This is a no-op for sheets already loaded. Safe to call repeatedly.

Args: project_path: Optional project path. If None, uses focused project.

Returns: Dictionary with: - total_sheets: Total SCH sheets in the project - loaded: Sheets newly loaded by this call - already_loaded: Sheets that were already resident - failed: Sheets that could not be opened

proj_get_bomA

Export a full BOM from the compiled project.

DATASHEET DISCIPLINE: The BOM is the canonical list of manufacturer part numbers in this design. The response carries _datasheet_guidance with per-part search queries. Before drawing any conclusion about a listed part, fetch and read its datasheet (WebSearch + WebFetch if not already at hand). Library metadata here is NOT authoritative.

Returns every component with designator, comment/value, footprint, library reference, and all pin-net connections.

Args: project_path: Optional project path. If None, uses active project. limit: Max components to return (default 1000).

Returns: Dictionary with "components" array and "count", "limit", "limit_hit" (true when the list stopped at limit with components left over), "skipped_nil", plus _datasheet_guidance + _datasheet_parts.

proj_get_component_infoA

Get full information about a single component.

IMPORTANT, if you need info for MORE THAN ONE component, use proj_get_component_info_many (batch). Looping this tool is a large wall-time sink, especially with with_pin_nets=True because each call compiles the project.

Performance note: pin nets are the only field that needs a project compile, and a stale-cache compile on a multi-sheet hierarchical project can take 30-60s. When you only need the component's value, footprint, library reference, sheet, or pin numbers/names (no nets), pass with_pin_nets=False and the call drops to sub-second by skipping Project.DM_Compile entirely. Use the default with_pin_nets=True only when you actually need to know what each pin is wired to.

with_parameters=False further skips the parameter iterator (Manufacturer / MPN / Value / Footprint paths). Marginal win compared to skipping the compile, but useful for the cheapest possible "is this designator placed?" probe.

timeout overrides the bridge's default poll timeout. The compile-bound full-fat call can legitimately need 60-90s on large projects, raise this when you keep with_pin_nets=True.

DATASHEET DISCIPLINE: Before making any claim about this component's pin function, voltage rating, timing, or electrical behavior, fetch its datasheet (WebSearch + WebFetch if needed). The parameters / comment / library metadata returned here are NOT authoritative, only the manufacturer datasheet is. _datasheet_guidance in the response carries the rule and a suggested search query.

Returns the component's designator, comment, footprint, library reference, parameters (when with_parameters=True), and every pin with name/number (and net assignment when with_pin_nets=True).

Args: designator: Component designator (e.g., "U1", "R8", "C3") project_path: Optional project path. If None, uses active project. with_pin_nets: When True (default) compile the project and emit the flattened net for each pin. Set to False to skip the compile and get a sub-second metadata-only response. with_parameters: When True (default) include the parameter dict. Set to False for the cheapest possible probe. timeout: Optional bridge poll-timeout override (seconds). Use a higher value (e.g., 90) when keeping with_pin_nets True on large hierarchical projects.

Returns: Dictionary with designator, comment, footprint, lib_ref, sheet, pins array (each with pin/name and, when requested, net), and (when requested) parameters dict, plus _datasheet_guidance + _datasheet_parts.

proj_get_component_info_manyA

Full per-component info for MANY designators in ONE round-trip.

PREFER THIS over looping proj_get_component_info. The compile (when with_pin_nets=True) happens once for the whole batch, not once per designator.

Set with_pin_nets=False to skip the compile entirely for a sub-second metadata-only response (footprint, lib_ref, parameters, pin numbers/names, sheet). Set with_parameters=False for the cheapest possible probe.

DATASHEET DISCIPLINE: parameters / comment / library metadata in the response are NOT authoritative. Before reasoning about any component's pin function, voltage rating, timing, or electrical behavior, fetch its manufacturer datasheet. The response carries _datasheet_guidance + _datasheet_parts.

Args: designators: List of component designators. project_path: Optional project path. If None, uses active. with_pin_nets: When True (default) compile once and emit the flattened net for each pin on every component. Set False to skip the compile. with_parameters: When True (default) include each component's parameter dict. timeout: Optional bridge poll-timeout override (seconds). Raise this when keeping with_pin_nets=True on large hierarchical projects.

Returns: Dict with components[], matched, requested, not_found[], plus _datasheet_guidance + _datasheet_parts.

proj_export_pdfB

Export the active document to PDF.

Args: output_path: Full path for the output PDF file

Returns: Dictionary confirming export

proj_cross_probeA

Jump to and highlight a component in the schematic or PCB.

Args: designator: Component designator to find (e.g., "U1", "R8") target: "schematic" or "pcb"

Returns: Dictionary confirming the operation

proj_get_statsA

Get design statistics from the compiled project.

Returns counts of sheets, components, pins, and nets.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with sheets, components, pins, nets counts

proj_get_board_infoA

Get PCB board information, outline vertices, layer stack, origin.

IT DOES NOT REQUIRE A PCB TO BE ACTIVE, which is what this used to claim. The handler resolves ANY open board, so with a schematic focused it answers about some other document. MEASURED: with a schematic active and a two-document project focused, it returned an outline belonging to neither, and gave no way to tell.

board_path in the reply names the document that actually answered. Check it before trusting the geometry, or activate the board you mean first.

Returns: Dictionary with origin_x, origin_y, board_path, outline (vertex array), and layers (active copper layer names)

proj_annotateA

Annotate schematic designators programmatically, no dialog, no user interaction.

Compiles the project, iterates every schematic sheet, collects all unlocked components, sorts them by the chosen order, and assigns sequential designators per alpha prefix (R1, R2, ... C1, C2, ... U1, U2, ...). Designator prefixes are preserved from the current value (e.g., "R?" or "R13" both keep the "R" prefix). Locked designators (Designator.IsLocked = True) are skipped. All changes are wrapped in SchServer.ProcessControl.PreProcess/PostProcess for undo support.

Args: order: Annotation traversal order: "down_then_across" (default: row-major top-to-bottom, left-to-right) "up_then_across" (row-major bottom-to-top, left-to-right) "across_then_down" (column-major left-to-right, top-to-bottom) "across_then_up" (column-major left-to-right, bottom-to-top) "none" (reset all designators to "?")

Returns: Dictionary with: - success: True - order: the order that was applied - renamed: count of components renumbered - reset: count reset to "?" (only for order="none") - skipped_locked: count of locked components left untouched - documents_processed: count of schematic sheets visited - programmatic: True (marks this as the non-interactive path)

proj_run_outputA

Generate manufacturing output files from the active PCB.

Note: These may open Altium's export dialogs for configuration.

Args: output_type: Type of output, "gerber", "drill", "pick_place", "ipc_netlist" output_path: Optional output directory/file path

Returns: Dictionary confirming generation

proj_get_focusedB

Get information about the currently focused project.

Returns: Dictionary with project information: - project_name: Name of the project - project_path: Full path to the project file - document_count: Number of documents in the project

proj_export_stepA

Export the open PCB to a STEP 3D model file.

The board is focused first, since the export acts on the focused view. If output_path is omitted, Altium may show a file-save dialog. generated is true only when a new file is at output_path afterwards; otherwise the reply says the export was dispatched and why nothing could be confirmed. An OutJob with an ExportSTEP output, run through proj_run_outjob, is the route whose result can always be checked.

Args: output_path: Full path for the output .step file (optional)

Returns: generated, plus dispatched and reason when no new file was found

proj_export_dxfA

Export the active PCB to DXF (AutoCAD) format.

Requires an active PCB document. If output_path is omitted, Altium may show a file-save dialog.

Args: output_path: Full path for the output .dxf file (optional)

Returns: Dictionary confirming the export

proj_export_imageA

Export the active schematic / PCB document, silent (no dialog).

Only format="pdf" is currently supported as a silent path. Raster formats (png/jpg/bmp) require a hand-configured OutJob with a Schematic Print -> Multimedia (Image) container; the Pascal side rejects them with IMAGE_FORMAT_UNSUPPORTED and points the caller at proj_run_outjob instead.

The PDF path uses Altium's WorkspaceManager:Print server process with FileName= - the same machinery as proj_export_pdf. No OutJob is loaded. The print runs against Client.CurrentView (the focused document), so ensure the schematic or PCB you want exported is the active tab before calling.

The Pascal side deletes any pre-existing file at output_path before running so the post-run FileExists probe is a true existence check. Python verifies again here and downgrades a Pascal "success" to EXPORT_FILE_MISSING if no file landed.

Args: output_path: Full path for the output file. The directory must already exist. The extension should be .pdf. format: Output format. Only "pdf" is silent today (default). "png", "jpg", "bmp" return an explicit IMAGE_FORMAT_UNSUPPORTED error. width: Kept for API compatibility. Unused by the PDF path. height: Kept for API compatibility. Unused by the PDF path.

Returns: Dictionary confirming the export with the resolved path, format, width, height, and the scoped schematic. Sets file_exists based on a Python-side existence check.

proj_list_outjob_containersA

List all output containers defined in an OutJob file.

OutJob files define output configurations (Gerber, PDF, BOM, etc.) organized into named containers. Use this to discover what outputs are available before running them with proj_run_outjob.

Args: outjob_path: Path to the .OutJob file. If omitted, uses the first OutJob found in the focused project.

Returns: Dictionary with "outjob_path" and "containers" array (each: name, type, group)

proj_run_outjobA

Execute a specific output container from an OutJob file.

First use proj_list_outjob_containers to list available containers, then run the desired one by name. Supports both GeneratedFiles (Gerber, drill, BOM, etc.) and Publish (PDF) container types.

SUCCESS MEANS A FILE WAS WRITTEN. Running a container proves only that Altium was asked to. One bound to a managed release, or with its outputs switched off, runs cleanly and writes nothing, and this used to report success: true with an output_dir that had never been created. output_dir is now checked afterwards, and success is true only when a file under it was written or changed after the run started.

That check covers output_dir and nothing else. A container that writes somewhere else reads as success: false, and reason says what was examined, so a caller who knows where the output really goes can look there.

Args: container_name: Name of the output container to execute outjob_path: Path to the .OutJob file. If omitted, uses the first OutJob found in the focused project. fresh_window_seconds: files modified up to this many seconds before the run started still count as written, for file systems with coarse timestamps. Default 5s.

Returns: success, container_name, container_type, output_dir, output_dir_exists, files_written (a count), written (up to 50 paths), and reason when nothing was written.

proj_run_outjob_allA

Run every container in an OutJob and report what each produced.

Lists containers via proj_list_outjob_containers, then runs each through proj_run_outjob in order. For each run, scans the OutJob's resolved output_dir afterwards and flags files whose mtime is later than the run started (newly_produced) so the caller can tell which files this particular container wrote.

Args: outjob_path: Path to the .OutJob. Omit to use the first one in the focused project. include_files: When True (default) the response includes a per-container file listing with sizes / mtimes / a newly_produced flag. fresh_window_seconds: Files modified within this many seconds before the container started are still considered "newly produced" (some output processes touch existing files instead of rewriting). Default 5s.

Returns: Dict with ok, outjob_path, containers_run (list of container names), results (per-container dicts each with container, container_type, output_dir, ok, files).

proj_generate_fab_packageA

Generate a fabrication package from an OutJob (Gerbers, NC drill, IPC-356, pick-and-place, assembly, BOM) and report every file produced.

Altium has no per-format export process for Gerber / NC-drill / IPC-356 / P&P: those exist only as OutJob output containers. This runs every container in the project's OutJob, scans each container's output directory, and returns a consolidated manifest. Optionally also exports a STEP 3D model and a DXF (which are separate PCB export processes, not OutJob containers).

Prerequisite: the project must have an OutJob whose fab containers are configured and enabled: the script cannot enable outputs that are off in the OutJob editor.

Args: outjob_path: path to the .OutJob; omit to use the first one in the focused project. include_step: also export a STEP 3D model of the active PCB. include_dxf: also export a DXF of the active PCB. fresh_window_seconds: files modified within this window before a container ran still count as newly produced.

Returns: {"ok", "outjob_path", "containers_run", "results" (per-container with output_dir + files), "extras" (step/dxf results), "all_files" (flat list of newly-produced file paths)}.

proj_list_variantsB

List all project variants with their component overrides.

Compiles the project and returns every variant definition including component variation details (Fitted / Not Fitted / Alternate) and any parameter overrides.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with "variants" array and "count". Each variant has name, description, and variations array (designator, kind, alternate_part, parameters).

proj_get_active_variantA

Get the currently active project variant.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with variant "name" and "description". Returns "[No Variations]" if no variant is active.

proj_set_active_variantB

Switch the active project variant.

Args: variant_name: Name of the variant to activate project_path: Optional project path. If None, uses active project.

Returns: Dictionary confirming the switch

proj_export_variant_matrix_csvA

Write the variant fitted/not-fitted matrix to a CSV.

Builds the conventional component-by-variant table: one row per flattened component, one column per variant, each cell Fitted / Not Fitted / Alternate. Unlike proj_list_variants (which lists only per-variant deviations), every component appears, so the file merges cleanly with a BOM in a spreadsheet.

Args: output_path: Destination .csv. Defaults to workspace/variant_matrix.csv. project_path: Optional project path. If None, uses the focused project.

Returns: {"output_path", "component_count", "variant_count"} or an error.

proj_print_all_variantsA

Export a PDF for every project variant.

Loops the project's variants; for each it sets that variant active and runs the (silent) PDF export, writing one <variant>.pdf per variant. The originally-active variant is restored at the end. Composes the existing proj_set_active_variant + proj_export_pdf paths -- no new export machinery.

Args: output_dir: Folder for the per-variant PDFs. Defaults to the workspace directory. project_path: Optional project path. If None, uses the focused project.

Returns: {"exported": [{variant, output_path, ok, error}], "count"} plus restored: the variant put back, or null if putting it back FAILED. Null is not cosmetic. This loop leaves the project on the last variant it exported, so a null means the active variant is not the one you started with, and anything variant-sensitive run afterwards (an export, a BOM, pcb_apply_dnp_paste_exclusion) will act on the wrong set. A failure also carries restore_error, active_variant_unknown and a note.

proj_create_variantA

Create a new project variant.

After creating, use proj_set_active_variant to switch to it, and obj_modify to configure component variations.

Args: name: Name for the new variant description: Optional description project_path: Optional project path. If None, uses active project.

Returns: Dictionary confirming creation with name and description

proj_delete_variantA

Delete a project variant, with every component variation in it.

THIS IS NOT REVERSIBLE FROM HERE. A variant's entries record which components are not fitted and which carry an alternate part, and DM_RemoveProjectVariant is the ONLY write in Altium's whole variant API. There is no scripted way to add a variation entry back, so deleting a variant with fifty entries costs fifty decisions that have to be re-made in the Variant Management dialog. The reply reports entries_removed so the size of that is on the record; read it with proj_list_variants first if the variant is not one you created.

The variant is addressed by name rather than index, because removing one renumbers the rest: two deletions by index taken from a single listing would remove the wrong second variant.

Args: variant_name: name of the variant to remove. project_path: optional project path; defaults to the focused project.

Returns: {"success": true, "variant_name": ..., "entries_removed": N, "variant_count_before": N, "variant_count_after": N, "verified": true}, or success: false with a reason when the variant is still listed afterwards.

proj_list_openA

List all currently open projects in the Altium workspace.

Returns: Dictionary with "projects" array (each: project_name, project_path, document_count) and "count"

proj_get_messagesA

Get all messages from the Messages panel (compile errors, ERC violations, etc.).

Compiles the project first so violation data is current, then returns every violation with its text, severity, and source document.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with "messages" array (each: message, severity, source) and "count"

proj_find_componentA

Search for components across all project sheets.

DATASHEET DISCIPLINE: Results carry _datasheet_guidance. If you're searching by a part number / comment to answer a technical question (pinout, rating, behavior), the matched parts' datasheets must be consulted before drawing any conclusion. Symbol metadata is not ground truth.

Performs a case-insensitive partial match against the chosen property.

Args: search_text: Text to search for (e.g., "U1", "100nF", "LM317") search_by: Property to search, "designator", "value", or "comment" (default "designator") project_path: Optional project path. If None, uses active project.

Returns: Dictionary with "results" array (each: designator, comment, footprint, lib_ref, sheet, location_x, location_y) and "count", plus _datasheet_guidance + _datasheet_parts.

proj_get_connectivityA

Get pin-to-net connectivity for a specific component.

IMPORTANT, if you need connectivity for MORE THAN ONE component, use proj_get_connectivity_many (batch). Looping this tool for a set of designators is the biggest wall-time sink in design-review workflows.

Compiles the project and returns every pin with number, name, net assignment, and electrical type.

Args: designator: Component designator (e.g., "U1", "R8") project_path: Optional project path. If None, uses active. force_recompile: SaveAll + invalidate cache + recompile before reading. Use when you need a guaranteed-fresh netlist.

DATASHEET DISCIPLINE: pin_name in the response comes from the symbol, which can be wrong. Before reasoning about the pin's function (input vs output, drive strength, voltage range, active level), fetch the manufacturer datasheet and verify against its pin-description table. The response carries _datasheet_guidance + _datasheet_parts.

Returns: Dict with designator, comment, sheet, pin_count, pins[], plus _datasheet_guidance + _datasheet_parts.

proj_get_connectivity_manyA

Pin-net connectivity for MANY components in ONE round-trip.

PREFER THIS over looping proj_get_connectivity.

DATASHEET DISCIPLINE: pin_name on every component in the response comes from the symbol and can be wrong. Before reasoning about pin functions, fetch each part's manufacturer datasheet. The response carries _datasheet_guidance + _datasheet_parts.

Args: designators: List of component designators. project_path: Optional project path. force_recompile: SaveAll + invalidate cache + recompile before reading. Use when you need a guaranteed-fresh netlist.

Returns: Dict with components[], matched, requested, not_found[], plus _datasheet_guidance + _datasheet_parts.

proj_force_recompileA

Flush all dirty docs, invalidate the compile cache, and recompile.

Use this when you need a guaranteed-fresh netlist, e.g. immediately before re-running a connectivity check the user has disputed. Returns prev / new compile tick so you can verify the recompile actually happened.

Returns: Dict with recompiled, prev_compile_tick, new_compile_tick, project path.

proj_get_compile_freshnessA

Report the age of the cached netlist and which docs are dirty.

Use this when you're about to disagree with the user about connectivity, first check how stale the netlist you're reading actually is, and whether any open editor docs haven't been saved yet. A dirty doc means the netlist does NOT reflect what the user is looking at.

Returns: Dict with compile_age_ms, compile_cached (bool), ttl_ms, open_doc_count, dirty_doc_count, dirty_docs[], project.

proj_import_documentA

Import a document into the focused project from an external path.

Copies the file into the project directory (if not already there), adds it to the project, and saves the project file.

Args: source_path: Full path to the source document (SchDoc, PcbDoc, etc.)

Returns: Dictionary with success status, source_path, and dest_path

proj_get_pathB

Get the full path of the currently focused project file.

Returns: Dictionary with project_path, project_dir, and project_name

proj_set_document_parameterA

Set a document-level parameter on a specific schematic sheet.

Useful for per-sheet title block data (e.g., "SheetTitle", "Revision"). If the parameter already exists it is updated; otherwise a new hidden parameter object is created on the sheet.

NOTE: the target sheet must already be loaded as a proper project member. Call proj_load_sheets once at the start of a batch; auto-opening from inside this tool risks detaching the sheet and rendering it as a "free document". If the sheet isn't loaded this tool returns NOT_LOADED.

The write is persisted to disk immediately via the IServerDocument API, no subsequent app_save_all is required.

Args: file_path: Full path to the schematic document (.SchDoc). Use Windows-style backslashes (e.g. C:\path\Sheet.SchDoc), not forward slashes. name: Parameter name value: Parameter value

Returns: Dictionary with file_path, name, value, and dirty=true (the in-memory flag; the disk write already happened, so no app_save_all is needed for this parameter).

proj_compare_sch_pcbA

Compare schematic and PCB component counts.

Compiles the project and compares physical component counts between the schematic and the primary PCB document, via the component mappings (matched + side-only) -- the same authoritative path as proj_get_differences. For the per-component breakdown of what is extra on each side, use proj_get_differences.

DATASHEET DISCIPLINE: If the diff reveals mismatched or missing parts and you're proposing a fix, the datasheets of the parts involved are authoritative on their pinout and behavior, fetch them before suggesting a footprint change or pin reassignment.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with sch_components, pcb_components, components_match (bool), and pcb_path. (Net-count comparison is not provided here; use proj_get_nets / pcb_get_nets.)

proj_sync_pcbA

Push schematic changes to PCB (ECO): Design ▸ Update PCB Document.

IMPORTANT: this is NOT silent. Altium's ECO (change-review) dialog is non-suppressible by design, so this fires the real ECO and then BLOCKS on a modal dialog until a human clicks "Execute Changes". Do not call it in an unattended/headless run: it will hang the Altium-side polling loop until someone interacts. (There is no documented silent flag; the prior implementation called a non-existent process id and silently did nothing.)

The server:

  1. Compiles the project and records before-state mappings (matched, extra-in-schematic, extra-in-pcb).

  2. Focuses the project's PCB and invokes WorkspaceManager:Compare (ObjectKind=Project, Action=UpdateMe): import the schematic's changes into that PCB. The modal ECO dialog opens here.

  3. After the user accepts, recompiles and reports the after-state delta (how many components were added/removed).

THE DIRECTION DEPENDS ON FOCUS, so the tool sets it. A schematic in focus makes Altium refuse with "Cannot compare a source document against its owner project"; the PCB in focus with Action=UpdateOther updates the SCHEMATIC from the board, which is what this tool used to send, and an ECO came up with the SchDoc as the affected document. Refuses with WRONG_FOCUS when the PCB cannot be focused. Before executing, check that the ECO's affected documents are the PcbDoc.

What the result does NOT tell you: whether the dialog was accepted. dialog_outcome_verified is always false, because the handler returns as soon as the modal closes and cannot see which button was pressed. components_added_to_pcb and its siblings are recounts, so they catch a change in component PRESENCE and miss everything else an ECO carries: footprint swaps, designator and parameter edits, net changes. Unchanged counts therefore mean "nothing was added or removed", not "nothing happened". To learn what the dialog actually says, call app_list_open_dialogs, which reads Altium over Win32 and so answers while the modal is blocking the bridge.

For unattended board population without a schematic, use pcb_place_components instead (places geometry only: see its note about leaving the project unsynced).

Returns: Dictionary with success, pcb_path, before/after mapping counts, components_added_to_pcb, components_removed_from_pcb, components_in_sync (component presence only, see above), and dialog_outcome_verified.

proj_sync_schematicA

Push PCB changes back to schematic (back-annotate ECO). Attempts silent execution.

Equivalent to Design > Update Schematic in Altium Designer. Mirror of update_pcb: compiles, records before-state mappings, invokes PCB:UpdateSchematicFromPCB with silent-mode parameter flags (DisableDialog, Silent, NoConfirm, AutoApply), recompiles, and reports the after-state delta. Modern Altium versions execute without a dialog; older versions may still require manual confirmation (flagged via dialog_may_have_opened).

Returns: Dictionary with success, pcb_path, before/after mapping counts, components_added_to_schematic, components_removed_from_schematic, in_sync, and dialog_may_have_opened flag.

proj_get_differencesA

Get detailed differences between schematic and PCB netlist.

Compiles the project and uses component mappings to find:

  • Matched components (present in both schematic and PCB)

  • Extra components in schematic (not yet in PCB)

  • Extra components in PCB (not in schematic)

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with matched_components, extra_in_schematic (array), extra_in_pcb (array), and in_sync (bool)

proj_lock_designatorA

Lock or unlock component designators to prevent re-annotation.

When locked, designators are preserved during annotation operations. Use designator="all" to lock/unlock all components on the active sheet.

Args: designator: Component designator (e.g., "U1", "R3") or "all" for all components lock: True to lock, False to unlock (default True)

Returns: Dictionary with designator, locked status, and count of affected components

proj_get_optionsB

Get project options: output path, hierarchy mode, compiler settings.

Returns configuration details including output directory, hierarchy mode, document counts, variant count, channel settings, and net naming options.

Args: project_path: Optional project path. If None, uses active project.

Returns: Dictionary with project_name, output_path, hierarchy_mode, logical_document_count, physical_document_count, variant_count, channel settings, and net naming options

lib_create_symbolA

Create a new schematic symbol in the active library.

Args: name: Component name designator_prefix: Default designator prefix (e.g., "U", "R", "C") description: Component description part_count: For multi-part components (quad op-amp, dual gate, etc), the number of sub-parts. Pins added via lib_add_pins with owner_part_id set to 1..part_count are assigned to that sub-part; owner_part_id=0 shares the pin across all sub-parts (the usual power-pin pattern on a quad op-amp).

Returns: Dictionary with created symbol information

lib_create_ic_symbolA

Create a COMPLETE, discipline-compliant IC symbol in ONE call.

You decide the functional grouping (inputs/power/control on the LEFT, outputs/status on the RIGHT -- discipline rule 18); this lays them out cleanly and emits the whole symbol: the body rectangle (Altium-yellow fill) plus every pin, grid-aligned with the top-left pin's wire-end at the origin (rule 14), in one create + one bulk add_pins + one rectangle.

Args: name: Component name. left_pins: Pins on the LEFT side, top-to-bottom. Each a dict with designator, name, and optional electrical_type (input/output/power/passive/...). right_pins: Pins on the RIGHT side, top-to-bottom, same shape. designator_prefix: e.g. "U". description: Component description.

Returns: Dict with the symbol name, pin count, body extent, and the per-step bridge results.

lib_create_passive_symbolA

Create a standard 2-pin PASSIVE symbol in ONE call.

Draws the recognizable glyph for a resistor / capacitor / polarized_capacitor / inductor / diode / led / crystal / fuse, with pin 1 on the left and pin 2 on the right. Composes lib_create_symbol + lib_add_pins + the glyph primitives (emitted as raw mils, so the LED's emission arrows and the crystal's resonator keep their sub-100-mil detail).

Args: name: Component name (e.g. "R_0402"). kind: "resistor" | "capacitor" | "polarized_capacitor" | "inductor" | "diode" | "led" | "crystal" | "fuse" (aliases r/c/cap_pol/electrolytic/cp/l/d/xtal/f accepted). designator_prefix: default by kind (R/C/L/D/Y/F) if omitted. description: Component description.

Returns: Dict with the symbol name and per-step bridge results.

lib_set_current_componentA

Make a named component the editor's current selection in the active SchLib.

Required before bulk-editing a specific component's pins, rectangle, or parameters via obj_modify / obj_batch_modify on a SchLib. The asymmetry it fixes: lib_get_component_details is a read-only fetch and does NOT update the editor's current component, so subsequent obj_modify on the SchLib's ePin / eRectangle / eParameter iterators silently hits whatever component was last UI-selected -- usually NOT the one you just read.

Use this between switching components: lib_set_current_component("MyIC") modify_objects("ePin", scope="active_doc", filter="Location.X=200", set="Orientation=2") lib_set_current_component("MyOtherPart") modify_objects("ePin", scope="active_doc", filter="Location.X=200", set="Orientation=2")

Args: component_name: Component name (LibRef) in the active SchLib.

Returns: Dict with success + name, or an error if no SchLib is active or the component name isn't found in it.

lib_add_pinsA

Add MANY pins to the current symbol in ONE call.

PREFER THIS over adding pins one at a time (there is no singular variant). A 48-pin IC symbol built one pin at a time would be 48 LLM turns; with this tool it's one turn + one PreProcess/PostProcess + one save.

Args: pins: List of pin dicts, each with: - designator (str, required) - name (str, required) - x, y (int, mils), pin endpoint - length (int, mils, default 200) - rotation (int, default 0), 0/90/180/270 - electrical_type (str, default "passive"), one of input/output/bidirectional/passive/open_collector/ open_emitter/power/hiz/io - hidden (bool, default False) - owner_part_id (int, optional). For multi-part components (e.g. quad op-amp). 1..part_count picks a specific sub-part; 0 shares the pin across all parts (typical for V+ / V- power pins on a quad). Omit for single-part symbols. - symbol_outer_edge (str, optional). IEEE decoration on the pin's outer edge. "dot" draws the inversion bubble of an active-low pin. Omit for none. - symbol_inner_edge (str, optional). Same, inner edge. "clock" draws the clock wedge. The two are independent, so an inverted clock sets both. Either field also accepts any other TIeeeSymbol name ("schmitt", "open_collector", "active_low_input", ...) or a bare ordinal. - show_name / show_designator (bool, optional). Whether the pin's name and number are DRAWN. Different from hidden, which hides the pin itself: a resistor shows both pins and neither label. Omit to leave Altium's default.

Example, one stage of a dual op-amp (sub-part 1) with shared power pins (sub-part 0): lib_add_pins(pins=[ {"designator": "1", "name": "OUT1", "x": 0, "y": 0, "rotation": 180, "electrical_type": "output", "owner_part_id": 1}, {"designator": "2", "name": "IN1-", "x": 0, "y": 100, "rotation": 180, "electrical_type": "input", "owner_part_id": 1}, {"designator": "3", "name": "IN1+", "x": 0, "y": 200, "rotation": 180, "electrical_type": "input", "owner_part_id": 1}, {"designator": "8", "name": "V+", "x": 0, "y": 400, "rotation": 180, "electrical_type": "power", "owner_part_id": 0}, ])

Returns: Dict with added, failed, total counts.

lib_add_symbol_rectangleA

Add a rectangle to the current symbol body.

ADD THE BODY BEFORE THE PINS. Altium exposes no z-order on schematic primitives, so drawing order is insertion order and there is no send-to-back to correct it afterwards. A rectangle given a fill_color is SOLID, and one added after the pins paints over the pin names; the only fix is rebuilding the symbol. Build order is body, then pins, then anything drawn on top.

Args: x1: First corner X in mils y1: First corner Y in mils x2: Opposite corner X in mils y2: Opposite corner Y in mils fill_color: Fill color index (-1 = no fill) border_color: Border color index

Returns: Dictionary confirming rectangle addition

lib_add_symbol_textA

Add MANY body-text items to the current symbol in ONE call.

PREFER THIS over adding text one at a time (there is no singular variant). Free text on a symbol is an Altium ISch_Label: the polarity marks, pin-group headings and "NC" annotations that carry meaning the geometry alone does not.

Args: texts: List of text dicts, each with: - text (str, required). Empty strings are refused, not placed: an empty label is invisible and unselectable, and only turns up later as a stray object. - x, y (int, mils) - rotation (int, default 0), 0/90/180/270 - font_size (int, default 10). Altium's own font size, the number its font manager takes. NOT mils, and not converted from mils: the relationship between the two is not documented anywhere this project can verify, so a conversion here would silently resize every item. - font_name (str, default "Arial") - bold, italic (bool, default False) - owner_part_id (int, optional). Same meaning as in lib_add_pins: 1..part_count picks a sub-part, 0 shares the text across all of them. Omit for single-part symbols.

Returns: Dict with added, failed, total counts.

lib_add_symbol_linesA

Add MANY lines to the current symbol body in ONE call.

PREFER THIS over adding lines one at a time (there is no singular variant). A 12-line LED diode glyph drawn line-by-line would be 12 LLM turns + 12 IPC round-trips + 12 redraw passes; this tool does it in one turn with a single PreProcess/PostProcess pair and one editor redraw at the end.

Args: lines: list of dicts, each with keys x1, y1, x2, y2 (mils, int), width (int 0-3, default 1). grid: snap grid (mils) for the line endpoints. Glyph lines do NOT need the 100-mil pin grid (rule 15), so the default is a finer 25 mils for clean glyph detail; pass 100 for the coarse grid or 0/1 to disable snapping.

Returns: Dict with added, failed, total counts.

lib_create_footprintC

Create a new PCB footprint in the active library.

Args: name: Footprint name description: Footprint description

Returns: Dictionary with created footprint information

lib_create_standard_footprintA

Create a COMPLETE standard footprint in ONE call.

Composes parametric geometry with the bulk authoring tools: makes the footprint, then emits every pad + the silkscreen outline + the courtyard rectangle in two batched round-trips. Supply the package's recommended land-pattern dimensions from the datasheet (this carries no part library -- the numbers you pass ARE the footprint).

Args: name: Footprint name (e.g. "SOIC-8_3.9x4.9"). family: "chip" (2-pin passive) | "sip" (single-row header) | "dual" (SOIC/SOP/SON/SOT/DIP) | "header" (2-row pin/box header, IDC, SWD/JTAG -- column-major odd/even numbering) | "tab" (power/tab package: SOT-223, DPAK/D2PAK, TO-220 -- signal pins one side + a large tab pad opposite, pass tab_w & tab_h) | "quad" (QFP/QFN) | "bga" (ball grid -- pass rows & cols). SOT-23 is "dual" with pin_count=3. Through-hole (DIP/header/TO-220) = any family with hole > 0. pin_count: total pads (2 for chip; even for header; n signal pins for tab; divisible by 4 for quad; ignored for bga). rows, cols: BGA ball grid dimensions (bga family only). exposed_pad: centre thermal/exposed pad size (mils) for QFN/QFP (quad family); 0 = none. Gets designator pin_count+1. tab_w, tab_h: tab pad size (mils) for the "tab" family; the tab gets designator pin_count+1 (tie it to a signal pin in the schematic). skip: BGA ball designators to omit (depopulation), e.g. ["A1","E5"] (bga family only). pitch: pad centre-to-centre within a row (mils). pad_w, pad_h: pad size (mils). TWO MEANINGS, kept for the callers that rely on them: pad_w is the pad's X size in every family but quad, so in dual (rows left and right) and chip it runs ACROSS the row, along the span, while in sip, header and tab it runs along the row; in quad each side turns the pad and pad_w runs ALONG the row. For a footprint from a package drawing use lib_create_footprint_from_package, which sizes the lands by IPC-7351B from the terminal's lead_length (along the span) and lead_width (across it). row_span: centre-to-centre between the two rows / opposite sides (mils). Required for dual and quad. shape: pad shape (default "roundrect"); corner_radius is its %. silk: draw a silkscreen body outline (TopOverlay). courtyard: clearance (mils) past the extent for the courtyard rectangle (Mechanical1); 0 disables it. body_w, body_h: package body for the silk outline; default to the pad extent. hole: drill size (mils). >0 makes through-hole pads (DIP / header / axial): pin 1 rectangular, the rest round.

Returns: Dict with the footprint name, pad/track counts, extent, and the per-step bridge results.

lib_create_footprint_from_packageA

Create a COMPLETE footprint in the open PcbLib from a package drawing.

Takes the package as the datasheet draws it, not a pad list: the lands come from IPC-7351B (toe, heel and side fillets with the tolerance stack-up) for the family and density asked for. Writes the pads (rounded rectangles with their corner ratio, rotation, holes, slots, non-plated holes), paste windows over an exposed pad with the pad's own paste closed, silkscreen kept clear of the copper with a pin 1 dot, the body outline and a .Designator on the assembly layer, the courtyard, and the footprint height from the package's maximum A. Origin at the package centroid. Every length goes in millimetres, unrounded.

It then READS THE PADS BACK (lib_get_pad_geometry) and compares count, centres, sizes, rotation, shape, holes and any manual paste or mask expansion with what was sent, within one internal unit. A difference is a failure, not a warning.

DATASHEET DISCIPLINE: transcribe the package from the manufacturer's drawing fetched in this conversation, each dimension as min/nom/max with its page and figure, and name the datasheet in source. The engine sizes lands from the tolerances, so a nominal-only dimension gives a worse pattern and says so in warnings.

Args: package: the PackageSpec as an object (or its JSON text): family (chip, melf, molded, sod, sot23, sot89, sot223, dpak, soic, son, qfn, qfp, plcc, bga, lga, crystal2, crystal4, dip, header, radial, axial, to220, to92, connector, custom) and the dimensions it needs, each a number, [min, max], [min, nom, max] or {min, nom, max, cite}. Axes: two-row parts have their rows LEFT and RIGHT, so lead_span_x is the tip to tip span across them (JEDEC E) and body_y the body along them (D); lead_length is the terminal along the span (L), lead_width across it (b). Exposed pad: ep_x/ep_y. source: {document, revision, page, ref, url}. name: footprint name; default the package's name, else a generated one (e.g. SOIC-8-P1.27N). density: L, N or M (least, nominal, most). Default: the package's own, N unless it says otherwise. description: appended to the generated description, which already names the family, the standard, the density and the source. assembly_layer, courtyard_layer: mechanical layers for those roles. Empty picks them BY ROLE from the library: the layer whose kind is "Assembly Top" / "Courtyard Top", else the one whose name says so (the same reading lib_audit_footprint_policies uses). With neither, the call refuses and lists the library's layers rather than guessing a number. drafting: overrides of the drafting rules, e.g. {"silk_width": 0.15, "ep_paste_coverage": 0.6, "mask_expansion": 0.05}. A non-zero mask expansion is written to every pad; at the default 1:1 the library's design rule decides. set_height: write the footprint height (package A max) with the existing height setter.

Returns: {ok, footprint, library_path, created, layers, readback, pads, courtyard_mm, warnings, notes, ipc, steps}. ok is true only when every step succeeded and the readback matched. A failure after the footprint was created says so: the footprint exists and is incomplete.

lib_add_footprint_padA

Add a pad to a footprint: the named one, or the current one.

Args: designator: Pad designator (e.g., "1", "2") x: X coordinate in mils y: Y coordinate in mils x_size: Pad X size (default 60 mil) y_size: Pad Y size (default 60 mil) hole_size: Drill hole size (0 for SMD). A drilled pad is forced through-hole (MultiLayer); a hole-less pad is SMD on layer. shape: Pad shape -- "round", "rectangular", "octagonal", or "roundrect" (rounded-rectangle, the modern IPC SMD default). An oval/obround is "round" with x_size != y_size. layer: Layer for an SMD pad ("TopLayer"/"BottomLayer"); ignored for through-hole pads (always MultiLayer). rotation: Pad rotation in degrees corner_radius: Corner radius percentage for shape="roundrect" (Altium stores RR radius as a %; 25 is typical). footprint_name: footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot, since otherwise the primitives land in whichever footprint another call left current. units: "mil" (default) or "mm", for every length and coordinate here. Millimetres are kept exact: 1.625 mm used to arrive as 64 mil and read back as 1.6256 mm.

Returns: Dictionary confirming pad addition

lib_add_footprint_padsA

Add MANY pads to the current footprint in ONE call.

PREFER THIS over looping lib_add_footprint_pad. A 64-pad QFP built one pad at a time is 64 LLM turns and 64 PreProcess/PostProcess+save cycles; with this tool it's one turn, one PreProcess/PostProcess, one save. Pad coords are NOT snapped to a grid (footprints use fine pitches like 50 mils / 0.5 mm).

Args: pads: List of pad dicts, each with: - designator (str, required) -- e.g. "1", "2", "A1" - x, y (int, mils), pad centre - x_size, y_size (int, mils, default 60) - hole_size (int, mils, default 0 = SMD) - shape (str, default "rectangular"), one of round / rectangular / octagonal / roundrect (modern IPC rounded-rectangle SMD). Oval = round with x_size!=y_size. - corner_radius (int %, default 25) for shape="roundrect" - rotation (float deg, default 0) - layer (str, default "TopLayer"). Only used for SMD pads (hole_size=0); a drilled pad is forced through-hole (MultiLayer). Use "BottomLayer" for bottom-side SMD. Optional, written only when given: - hole_length (slot length, above hole_size: a slotted hole hole_size wide, along the pad's X before rotation) - plated (false: a non-plated hole) - paste_expansion, mask_expansion (a manual expansion that overrides the design rule; minus the larger pad side closes the paste opening) The reply counts them (slots, unplated, expansions) and any write this Altium build refused (property_failures).

Example, a 4-pad 0402 + corner pad in one call: lib_add_footprint_pads(pads=[ {"designator": "1", "x": -25, "y": 0, "x_size": 30, "y_size": 40}, {"designator": "2", "x": 25, "y": 0, "x_size": 30, "y_size": 40}, ])

footprint_name picks the footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot, since otherwise the primitives land in whichever footprint another call left current.

units is "mil" (default) or "mm" for every pad in the call: one land pattern, one unit, so half a footprint cannot be drawn in the wrong one. Millimetres are kept exact.

Returns: Dict with added, failed, total counts.

lib_add_footprint_trackA

Add a track to the current footprint (for silkscreen/courtyard).

Args: x1: Start X in mils y1: Start Y in mils x2: End X in mils y2: End Y in mils width: Track width in mils layer: Layer name. Default "TopOverlay" (silkscreen). Any Altium layer is accepted, e.g. "BottomOverlay" or "Mechanical1".."Mechanical16" for courtyard / assembly outlines. footprint_name: footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot. units: "mil" (default) or "mm", for every length and coordinate here, kept exact rather than rounded to whole mils.

Returns: Dictionary confirming track addition

lib_add_footprint_tracksA

Add MANY tracks to the current footprint in ONE call.

PREFER THIS over looping lib_add_footprint_track. A silkscreen outline + assembly outline is 8+ segments; built one at a time that's 8+ LLM turns and 8+ PreProcess/PostProcess+save cycles. Batched it's one turn, one PreProcess/PostProcess, one save.

Args: tracks: List of track dicts, each with: - x1, y1, x2, y2 (int, mils) -- endpoints - width (int, mils, default 10) - layer (str, default "TopOverlay" silkscreen). Any Altium layer name, e.g. "BottomOverlay" or "Mechanical1".."Mechanical16" for courtyard/assembly.

Example, a 100x60 mil silkscreen rectangle in one call: lib_add_footprint_tracks(tracks=[ {"x1": -50, "y1": -30, "x2": 50, "y2": -30}, {"x1": 50, "y1": -30, "x2": 50, "y2": 30}, {"x1": 50, "y1": 30, "x2": -50, "y2": 30}, {"x1": -50, "y1": 30, "x2": -50, "y2": -30}, ])

footprint_name picks the footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot, since otherwise the primitives land in whichever footprint another call left current.

units is "mil" (default) or "mm" for every track in the call, kept exact rather than rounded to whole mils.

Returns: Dict with added, failed, total counts.

lib_add_footprint_regionsA

Add solid polygon regions to a PcbLib footprint in ONE call.

What a footprint needs beyond pads and lines: paste windows over an exposed pad (on TopPaste, with the pad's own paste closed by a negative paste_expansion in lib_add_footprint_pads), or any filled shape on a mechanical or overlay layer.

Args: regions: list of {"layer": ..., "points": [[x, y], ...]}, three or more points each, relative to the footprint origin. The layer is REQUIRED: there is no default, because a solid shape defaulted onto copper is a short. footprint_name: footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot. units: "mil" (default) or "mm", for every point, kept exact.

Returns: Dict with added, failed, bad_points, unknown_layers, total. A region refused here before sending is listed in refused with the reason.

lib_add_footprint_arcA

Add an arc to the current footprint.

Args: x_center: Center X in mils y_center: Center Y in mils radius: Arc radius in mils start_angle: Start angle in degrees end_angle: End angle in degrees width: Line width in mils layer: Layer name. Default "TopOverlay" (silkscreen). Any Altium layer accepted, e.g. "Mechanical1".."Mechanical16" for pin-1 / assembly markers. footprint_name: footprint to write into. Empty uses the editor's current footprint; a name makes that footprint current first and refuses if it cannot. units: "mil" (default) or "mm", for every length and coordinate here, kept exact rather than rounded to whole mils.

Returns: Dictionary confirming arc addition

lib_add_footprint_textA

Add a text primitive to a PcbLib footprint.

Trap baked into this handler: in a PcbLib, adding a primitive only to the footprint does NOT register it with the placement editor -- the text shows up only after a save+reload. This tool adds the text to BOTH the footprint and the underlying board, then broadcasts PCBM_BoardRegisteration to both, which is the pattern that registers the primitive reliably.

Args: text: The string to place. Required. x, y: Coordinates in mils, relative to the FOOTPRINT's origin, which is what the handler has always actually done (Footprint.X + MilsToCoord(x)). The old wording said "board origin" and that was wrong: a PcbLib footprint sits at Altium's library origin, 50000 mils out, so the two readings differ by more than a metre. The pad, track and arc tools wrote absolute board coordinates and produced footprints whose geometry was nowhere near them; they now match this one. size: Text height in mils. 50 is a common silkscreen size; drop to 30-40 for tight footprints. width: Stroke width in mils. 8 reads cleanly at 50 mil size; scale with size. rotation: Rotation in degrees (0, 90, 180, 270 typical). layer: Layer name resolved by GetLayerFromString (TopOverlay, BottomOverlay, TopSolder, BottomSolder, Mechanical1 ... Mechanical32). use_ttfont: True for TrueType; default False is the vector stroke font that fab houses prefer. mirror: Mirror the text. Set this for anything on a BOTTOM layer, or it reads backwards on the finished board; audit_find_mirrored_pcb_text reports both halves of the mistake (bottom text unmirrored, top text mirrored). library_path: Optional .PcbLib to focus before adding. Defaults to the active document. component_name: Optional footprint name to switch to before adding. Defaults to the currently active footprint. units: "mil" (default) or "mm", for every length and coordinate here, kept exact rather than rounded to whole mils.

Returns: Dict with success, footprint, text, layer, x, y.

lib_extract_intlibA

Extract .SchLib + .PcbLib sources from an .IntLib.

Opens the integrated library in Altium, runs the editor's Extract Sources command, then probes Altium's conventional output locations (a sibling folder named after the IntLib base name, falling back to the IntLib's own directory) to report which source files actually appeared on disk.

After this returns with sch_lib_found and / or pcb_lib_found true, the produced files can be opened with the rest of the library toolset -- lib_get_components, lib_get_footprints, lib_copy_component, etc. -- by passing the reported sch_lib_path / pcb_lib_path as the library_path argument.

Args: intlib_path: Absolute path to the .IntLib file.

Returns: Dict with intlib_path, extract_dir, sch_lib_path, sch_lib_found, pcb_lib_path, pcb_lib_found. A _found flag of False means Altium did not produce that source -- either the extract command name needs adjusting for the local Altium build, the IntLib does not contain that kind of source, or write permissions prevented the dump.

lib_get_footprintsA

Enumerate every footprint in a PcbLib.

PcbLib counterpart to lib_get_components for SchLibs. Walks the PCB library with IPCB_Library.LibraryIterator_Create and returns one entry per footprint with its name and description.

Args: library_path: Optional .PcbLib path to focus first. Defaults to the focused document.

Returns: Dict with library_path, count and footprints (a list of {name, description}).

lib_audit_footprint_policiesA

Sweep a PcbLib and flag footprints that break the library's own conventions: inconsistent layer usage, pad rules, pin-1 markings, courtyard, silkscreen, 3D models, designator style.

For each policy dimension it learns the library's DOMINANT convention across all footprints and reports every footprint that deviates (so it works on any house style without hard-coded rules). Pass policy to pin a dimension to a required value instead, e.g. {"silk_layer": "Top Overlay", "courtyard": true}.

Each finding names the footprint, the dimension, and the expected vs actual value: enough to drive a fix, not just a report.

Designators are checked for presence, layer, height, and for sitting on the footprint's AVERAGE PAD CENTRE rather than the library origin (which is arbitrary). Tune the centring window with policy={"designator_center_tol": <mils>}.

Geometry is read with the bulk library.get_library_geometry command, which walks the library once and pages the result. A library of ~1000 footprints is one pass and a handful of round trips rather than one round trip (and one full library re-scan) per footprint.

Args: library_path: Optional .PcbLib to focus first; defaults to the focused library. policy: Optional explicit conventions to enforce; omitted dimensions are inferred from the library.

Returns {library_path, footprint_count, conventions, findings, summary}.

lib_convert_designators_to_strokeA

Convert every TrueType .Designator in a PcbLib to a stroke font.

A TrueType PCB text will not persist a position change made through XLocation: the write reads back changed, then Altium recomputes the position from the TrueType layout on reload and it reverts. That is why a handful of designators per library refuse to centre. Bold/Italic are TrueType-only attributes (a stroke text cannot be bold), so a bold or italic designator is the reliable TrueType tell.

This clears UseTTFonts, Bold and Italic on each such designator, reads back to confirm, saves, and reloads. Afterwards those designators are stroke and lib_fix_designators can centre them.

Returns {library_path, designators, converted, names, saved}.

lib_reload_libraryA

Close and reopen a PcbLib so Altium rebuilds its caches from disk.

IPCB_Text.BoundingRectangle is populated when the document loads and is NOT refreshed when a text moves or is resized, not even by GraphicallyInvalidate. Anything that reads a text's extent after writing to it therefore gets the OLD box. Reload between a write and the next read.

SAVE FIRST: this does not save, and closing a dirty document loses the edits or raises a prompt. lib_fix_designators already does this.

lib_probe_designatorA

Diagnostic: dump one footprint's raw designator geometry.

Read-only. Returns the footprint origin and bounding rectangle, the pad extents, and the .Designator anchor, bounding rectangle, size and width: all in native TCoord. Use it to establish what IPCB_Text.BoundingRectangle actually measures before trusting it to centre anything.

Returns {footprint, fp_x, fp_y, fp_rect, pad_count, pad_rect, desig_anchor, desig_rect, desig_size, desig_width, desig_text}.

lib_fix_designatorsA

Bring every footprint's .Designator onto the library's OWN convention: the layer, height and centring the majority already use.

Nothing is hard-coded. The target layer, its raw ordinal, the norm height and the centring tolerance are all inferred from this library (see lib_audit_footprint_policies); policy overrides any of them. "Centred" means on the AVERAGE PAD CENTRE, not the library origin.

DEFAULTS TO A DRY RUN. With dry_run=True (the default) it reports exactly which footprints it would touch, on which layer, and at which coordinates, and writes nothing. Pass dry_run=False to apply.

Applying RUNS TO CONVERGENCE: apply, save, reload the PcbLib, re-plan, repeat until nothing is left. The reload is mandatory, not cosmetic: Altium caches each text's bounding rectangle at load and never refreshes it when the text moves or is resized, so a second pass without a reload would centre against stale geometry and shove designators off the part. Resizing a text also changes its box, so height fixes and centring settle over successive passes rather than one.

PARTIAL SUCCESS IS EXPECTED AND REPORTED, NOT HIDDEN. A small number of designators (a handful per library) do not persist a position change through Altium's save for reasons not yet diagnosed: the write reads back changed but the value reverts on reload. Rather than loop forever or claim success, the tool stops when a pass makes no progress and returns converged: false with the offending footprints in unrepairable for a human to centre by hand. Everything else is applied. applied counts only writes confirmed by read-back, never attempts.

Args: library_path: .PcbLib to fix; defaults to the focused library. dry_run: report the plan without writing (default True). fix_layer: move designators off the convention layer onto it. fix_center: re-centre designators beyond the library's tolerance. fix_height: also normalise text height to the library norm. add_missing: create a .Designator on footprints that lack one. policy: explicit conventions, e.g. {"designator_center_tol": 50, "designator_height": 20}.

Returns {library_path, dry_run, conventions, planned, applied, created, passes, converged, failed, skipped, actions}.

lib_update_footprint_heights_from_3dA

Sweep the active PCB Library: for every footprint, find the tallest 3D body and write its OverallHeight to Footprint.Height.

Footprint.Height is what Altium's placement-collision DRC uses to enforce height-clearance rules (don't place a tall electrolytic under a low overhang; don't sandwich an LCD connector under another PCB in a multi-board stack-up). Libraries shipped from vendors without explicit heights default to 0 which makes the DRC silently no-op -- a real production risk caught only at first-article assembly.

Args: mode: raise (default) only ever increases a height, so a hand-set "I know this part is 5mm despite the model being 3mm" survives. match also LOWERS one to the model.

Reach for match when heights are too TALL, which raising cannot fix and which is the more damaging fault: a footprint claiming 50mm when the part is 3mm fails placement-collision DRC against everything near it and blocks placements that are fine, where a too-low height merely fails to catch a real collision. To correct one footprint, or one with no model at all, use lib_set_footprint_height.

Safety:

  • NEITHER MODE WRITES ZERO. A footprint with no 3D body yields no measurement, and writing the 0 that implies would silently disable the very DRC rule this arms. Those come back as without_model with their names.

  • Does NOT save the library; review the items[] diff and save in Altium.

Returns: Dict with mode, inspected, updated, lowered, without_model, without_model_names, and items ({name, old_height_mm, new_height_mm} per footprint).

lib_set_footprint_heightA

Set one footprint's Height directly, up or down.

The sweep can only derive a height from a 3D body, which leaves two cases it cannot serve: a part with no model, and a part whose model is wrong. There was no setter at all before this, so a footprint carrying an absurd height could be read and not corrected.

WHY TOO TALL IS WORSE THAN TOO SHORT. Footprint.Height drives placement-collision DRC. A footprint claiming 50mm when the part is 3mm fails against everything near it and blocks placements that are fine. A too-low height only fails to catch a real collision. The first floods the report and gets the rule switched off; the second is quiet.

ZERO IS ACCEPTED AND IS NOT NEUTRAL. It disables the rule for that footprint rather than relaxing it, so nothing is ever flagged against it however tall the real part is. The reply says so when zero is written.

Args: height_mm: the height in MILLIMETRES, not mils. Must be non-negative. footprint_name: which footprint. Empty uses the library's current component.

Returns: {name, old_height_mm, new_height_mm, changed, saved, save_note, note}. saved is always false: the library is modified in memory and left for you to review and save.

lib_link_footprintA

Link a footprint to a schematic component.

With replace=True (the default) any existing footprint (PCBLIB) model on the component is removed first, so re-linking replaces rather than appends. Pass replace=False to keep prior footprint models and add another (the old append behaviour, which bloats a component with duplicate models).

Args: component_name: Name of the schematic component. Resolved by library reference and made current before linking, so the model lands on the symbol you name rather than on whatever was last created. Leave empty to target the current component. footprint_name: Name of the footprint to link footprint_library: NOT APPLIED, accepted only so existing callers keep working. The footprint is bound by NAME via the implementation's datafile link, with a deliberately EMPTY location: passing a .PcbLib path there wedges AD26 (see Lib_LinkFootprint in Library.pas). Install the library and keep footprint names unambiguous instead. replace: remove existing footprint models before adding (default True)

Returns: Dictionary confirming link

lib_link_3d_modelA

Link a 3D STEP model to a PcbLib footprint.

Loads the STEP geometry into the footprint as an IPCB_ComponentBody (the real 3D body that drives the 3D view and the height/placement DRC), so the active document must be the .PcbLib that contains the footprint. component_name selects the footprint by name (empty uses the library's current footprint).

Args: component_name: Name of the footprint in the active PcbLib model_path: Path to the 3D model file (.step, .stp); must exist offset_x: X offset in mils from the FOOTPRINT's origin. The body is always moved onto that origin first: it arrives at the board origin, and a footprint open in the editor sits at Altium's library origin, about 50000,50000 mil, so an offset of 50000 used to be needed to bring it back. Do not add that any more. offset_y: Y offset in mils, applied with offset_x offset_z: Z offset in mils, sets the body's StandoffHeight. This is the common adjustment: lifting a connector body off the board so it sits on its pads rather than through them. rotation_x: NOT APPLIED, accepted for signature stability. Set it in the library editor after linking. rotation_y: NOT APPLIED, same reason as rotation_x. rotation_z: NOT APPLIED. This one used to assign Body.Rotation, and IPCB_ComponentBody has no such property on AD26 26.9.1.9. MEASURED: it raised "Undeclared identifier: Rotation", which DelphiScript cannot catch, so the Try around it never fired and the resulting modal took the whole polling loop down with it. Passing a rotation used to cost you the bridge. The rotation lives on the MODEL, not the body, and has to be set before the model is attached (Model.SetState in AutoSTEPplacer.pas). Its four arguments are undocumented, so they are not guessed at here. Rotate in the library editor for now.

Returns: Dict with success, footprint, model, footprint_origin_mils and moved_to_footprint_origin (false means the body may sit at the board origin, away from the footprint), and applied -- which adjustments were actually written to the body (standoff_height, rotation_z, offset_xy). Check it rather than assuming: each assignment is individually guarded, so one failing does not fail the call. rotation_z is now ALWAYS false, see above. standoff_height is confirmed live; offset_xy is not yet.

A ``false`` means the adjustment did not happen, which
covers both a rejected assignment and an argument left at
its default of 0: the handler skips a zero rather than
writing it. ``offset_xy`` covers ``offset_x`` and
``offset_y`` together and is true if either is non-zero.
lib_auto_link_3d_modelsA

Batch-link STEP models to footprints by matching file names.

Enumerates every footprint in the PcbLib, scans model_dir for .step/.stp files, and links each footprint whose name matches a model file's stem (case-insensitive). Builds on lib_link_3d_model so the same caveat applies: offsets/rotations are not set, adjust in the library if a model needs repositioning.

Args: model_dir: Folder containing .step/.stp model files. library_path: Optional .PcbLib to focus first; uses the focused document otherwise.

Returns: {"linked": [{footprint, model}], "unmatched_footprints": [...], "unused_models": [...]}.

lib_get_componentsA

Get all components in a library.

Default fast path returns only the metadata that the ILibCompInfoReader exposes directly: name, alias_name, part_count, description. That path scales linearly with file IO and finishes in well under a second on typical libraries.

Setting with_parameters=True adds each component's full parameter dict (Manufacturer, Value, Footprint, etc.) to the result. That branch calls GetState_SchComponentByLibRef once per symbol and iterates parameters, which is O(N) in the live SchLib document and is what makes the call slow on libraries with many hundreds of components. Use it when you need the parameters; for a single symbol's parameters, prefer lib_get_component_details.

Setting with_designator=True adds each component's DEFAULT designator (Component.Designator.Text, e.g. "U?" / "R?" / "IC?"). The CompInfoReader fast path does NOT expose the designator, so this also loads each live symbol (same cost driver as with_parameters) but skips parameter iteration, keeping the payload small. Intended for library-wide designator-consistency audits.

Args: library_path: Path to library (uses active library if not specified) with_parameters: If True, include each component's parameter dict (slow on large libraries). Default False. with_designator: If True, include each component's default designator string (slow on large libraries; smaller payload than with_parameters). Default False.

part_count HERE IS NOT TRUSTWORTHY. It comes from the CompInfoReader, and MEASURED on AD26 it reported 2 for a symbol created single-part whose every pin carries OwnerPartId 1, while lib_get_component_details reported 1 for an identically created symbol. The two readers disagree and only the discrepancy is established, not a conversion between them, so no correction is applied here rather than guess one. Use lib_get_component_details when the part count matters.

Returns: Dictionary with count and components list. Each component carries index, name, alias_name, part_count, description, (only when with_parameters is True) parameters, and (only when with_designator is True) designator.

``index`` is a stable addressing key (position in library
order). Pass it as ``component_index`` to lib_get_component_details
/ lib_delete_component / lib_rename_component to reach a
component whose ``name`` contains bytes you cannot reproduce
(an embedded double-quote, or a control char from a broken
import), the case where matching by name fails.
lib_searchA

Search open SchLib documents and installed database libraries.

OPEN .SchLib FILES AND INSTALLED DATABASE LIBRARIES (DbLib) ARE SEARCHED. Query libraries (SVN DbLib), integrated libraries (IntLib), workspace components and installed .SchLib files that are not open are not, so 0 hits is not "no such part". The reply says what was searched (libraries_searched, DbLibs included) and lists every installed library it could not read (not_searched, with the reason), with a coverage_note when anything was skipped.

DbLib rows join results after the SchLib matches, with what is left of limit, each marked source: "dblib" and carrying its table, key, symbol and footprints; SchLib matches are marked source: "schlib". Every column of a row is matched, so a manufacturer part number or a value finds the part. A DbLib that cannot be connected stays under not_searched with the reason, and dblib_searches says what each DbLib search covered. Use lib_dblib_search to restrict to one table or a few columns, and lib_dblib_get_record for every column of one row.

Case-insensitive substring match. Walks every .SchLib that is a member of any open project, plus every standalone .SchLib in the workspace's free-documents area. Each library is read via CreateLibCompInfoReader so the search is fast even with many libraries open: it only loads symbols when search_type is "parameters".

IT READS THE FILE ON DISK, NOT THE EDITOR. A component created in this session and not yet saved WILL NOT BE FOUND, and the reply is an ordinary empty result with nothing to say why. MEASURED: a symbol that lib_get_component_details returned in full was absent here until a save. Save first, or use lib_get_component_details / lib_get_pin_list, which read the live document.

DATASHEET DISCIPLINE: Matches carry _datasheet_guidance. Before recommending any matched part as a replacement or answer, fetch its datasheet (WebSearch + WebFetch). Do not recommend based on symbol metadata alone.

Args: query: Substring to match against component name / alias / description (case-insensitive). search_type: "all" (default, matches name / alias / description), "name", "description", or "parameters" (slow, also walks each candidate's parameter dict via the live symbol). library_path: Optional path to a single .SchLib, or to a single .DbLib, to restrict the search to. When omitted, searches every open .SchLib and every installed DbLib. limit: Cap on returned matches (default 100), SchLib and DbLib together. include_dblibs: also search the installed database libraries (default True). Their rows are read through the connection each DbLib declares, every row of every enabled table up to lib_dblib_search's row bound, so a large database makes the search slower; False keeps it to open .SchLib files and lists the DbLibs under not_searched. A library_path naming a .DbLib searches that one either way.

Returns: Dict with query, search_type, count, limit, truncated (True when count == limit, or when a DbLib search stopped early), and results, a list of {name, alias_name, description, library_path, part_count, source} per match (a DbLib row adds table, key_field, symbol_ref, symbol_library, footprints, matched_field, and its name is the row's key), plus _datasheet_guidance + _datasheet_parts; and libraries_searched, not_searched (each {library_path, library_type, reason}), dblib_searches and coverage_note as above. Either list is None when it could not be read.

lib_dblib_infoA

What a database library (.DbLib) declares, and how it connects.

A DbLib holds no symbols. It names a database (Access, Excel, SQL Server, any ODBC or OLE DB source) and lists the tables whose rows are the components; each row names a symbol in a .SchLib and footprints in .PcbLibs. This reads the .DbLib file itself, inside Altium, and with with_fields opens the database read-only to list each table's columns. Call it first on an unfamiliar DbLib: it says which column is each table's key, which carries the symbol and which the footprints, which is what lib_dblib_get_record and sch_place_dblib_component take.

THE PASSWORD IS NEVER RETURNED. connection.redacted is the connection string with every Password / Pwd value replaced by ***, and has_password says whether there was one.

HOW EACH COLUMN WAS FOUND IS STATED. key_field_source is key_setting or where_clause when the DbLib names it, part_number_column when a column is called Part Number (Altium's default), or first_column as a last resort; treat the last as a guess. settings is the table's section of the file as written, for anything the parsed fields do not cover.

Args: library_path: Full path of the .DbLib (or .SVNDbLib) file. lib_get_installed_libraries lists installed ones with library_type "database". with_fields: True (default) connects to the database to list every table's columns. False reads the file only and opens no connection: use it when the database is slow or unreachable, or to read the connection settings alone.

Returns: {"library_path", "connection": {"provider", "kind" (access / excel / sql_server / odbc / oracle / other), "data_source", "has_password", "redacted", "read_only"}, "left_quote", "right_quote", "search_path", "table_count", "tables": [{"name", "enabled", "schema", "key_field", "key_field_source", "key_field_found", "symbol_ref_field", "symbol_library_field", "description_field", "footprint_fields": [{"ref_field", "library_field"}], "fields" (null when not read), "field_count", "error", "settings"}], "sections", "fields_included", "connected" (null when no connection was attempted)}.

lib_dblib_searchA

Search database libraries (.DbLib) for components, read-only.

Case-insensitive substring match against the columns of every row of every enabled table, in one DbLib or in every installed one. lib_search already folds DbLib rows into its own results; this is the direct form, for restricting to one table or a few columns, or for raising the row bound on a large database.

THE QUERY NEVER REACHES THE DATABASE. Each table is read with a plain SELECT of the DbLib's own declared table and every row is matched inside Altium, so a query holding quotes, wildcards or SQL is matched as the text it is. The cost is that every row is read: max_rows bounds the total, rows_scanned reports it, and scan_capped says the bound cut the search short.

Args: query: Text to find (case-insensitive substring). library_path: One .DbLib to search. Omitted, every installed database library is searched. table: One table the DbLib declares (as lib_dblib_info names it). Omitted, every enabled table. fields: Column names to match. Omitted, every column. A name a table lacks is reported under unknown_fields. limit: Cap on returned rows (1 to 1000, default 50). max_rows: Cap on rows read in total (default 20000).

Returns: {"query", "count", "limit", "truncated", "rows_scanned", "scan_capped", "libraries": [{"library_path", "searched", "error", "tables_searched", "tables_failed", "unknown_fields"}], "results": [{"library_path", "table", "key", "key_field", "symbol_ref", "symbol_library", "footprints": [{"ref", "library"}], "description", "matched_field", "matched_value"}]}. A library's error is a code (READ_FAILED, TABLE_UNKNOWN, NO_TABLES, NO_CONNECTION_STRING, CONNECT_FAILED, LIMIT_REACHED); the reason text never carries the connection string.

lib_dblib_get_recordA

Every column of one row of a database library table.

The row is found by its key, the value in the table's key column (key in a lib_dblib_search result; the column is key_field in lib_dblib_info). The key is passed to the database as a parameter, never as SQL text. lookup says how the row was found: parameter when the database matched it, scan when the parameterised query could not run and the table was read and compared instead.

Args: library_path: Full path of the .DbLib file. table: A table the DbLib declares. key: The row's key value. key_field: Optional column to match instead of the DbLib's own key column; it must be a column of the table.

Returns: {"library_path", "table", "key", "key_field", "key_field_source", "lookup", "found", "match_count", "rows_scanned", "scan_capped", "symbol_ref", "symbol_library", "footprints": [{"ref", "library"}], "description", "fields": {column: value}}. found False is an answer, not an error: the key is not in that table. match_count above 1 means the key is not unique and the first row is shown.

sch_place_dblib_componentA

Place one database library (DbLib) part on a schematic sheet.

Acts on a .SchDoc: the active sheet, or sheet_path. The part is built by Altium from the database row (symbol, footprints and parameters, with its database link) and dropped at (x, y). NOT INTERACTIVE: nothing is attached to the cursor and nothing waits for a click.

THE DbLib MUST BE AVAILABLE TO ALTIUM. Altium finds a DbLib part by the library's file name among its available libraries, so the DbLib has to be installed (lib_install_library) or in the project. The call refuses before touching the sheet when it is not, or when table is not one the DbLib declares.

database_linked reads the placed part's DatabaseTableName back: True means the part carries its database link, False that Altium built it without one. lib_reference is the symbol it used.

Do not draw a schematic part by part with this. A sheet comes from a DesignPlan through design_execute_plan; this is for adding a part a person picked from their company library.

Args: library_path: Full path of the .DbLib file. table: A table the DbLib declares. key: The row's key value (key in a lib_dblib_search result). x, y: Location in mils. rotation: 0, 90, 180 or 270. designator: Optional designator to stamp on the part. sheet_path: Optional .SchDoc path; default the active sheet. part_id: Which part of a multi-part component to place, from 1 (A). Left out, Altium's default part is placed.

Returns: {"placed", "library_path", "table", "key", "lib_reference", "database_table", "database_linked", "x", "y", "rotation", "part_id", "part_count", "part_id_applied", "designator"}. part_id is read back off the placed part; a part_id above part_count is not applied and part_id_applied says so.

lib_get_component_detailsA

Get full inspection of one library component in a single call.

Returns metadata, every pin, every parameter (as a flat dict), AND visual-style records for the designator, comment, pins, and each parameter (font_id, color, is_hidden, x, y, orientation, justification). The integer font_id can be expanded to {name, size, bold, italic} via obj_get_font_spec when style detail is needed; the round-trip default keeps it compact.

If library_path is provided and isn't already focused, the library is opened (focus changes), so the next lib_* call operates on it without an explicit open. Saves are deferred, opening doesn't write anything to disk.

DATASHEET DISCIPLINE: This response is the highest-density device-fact surface in the library API (pins, parameters, Manufacturer/MPN). Treat every value as a hint to find the manufacturer datasheet, not as ground truth. The response carries _datasheet_guidance and _datasheet_parts, fetch the PDF and cite a section/page before stating any pin or rating.

Args: component_name: Component LibRef as it appears in the .SchLib. library_path: Optional .SchLib full path. When omitted the currently focused library is used. component_index: Alternative to component_name. The integer index from lib_get_components. Use this to reach a component whose LibReference carries bytes you cannot reproduce (an embedded double-quote, or a control char left by a broken import) -- the name never has to round trip through the caller. Provide exactly one of component_name / component_index.

Returns: Dict with: - name, library_path, description, alias_name, part_count, pin_count - designator: {text, font_id, color, is_hidden, x, y, orientation, justification} - the on-canvas designator label (NOT just the prefix string). - comment: {text, font_id, color, is_hidden, x, y, orientation, justification} - the on-canvas comment / value label. - pins: list of {designator, name, electrical_type, x, y, orientation, hidden, label_hidden}. Pin font / color are not exposed by the Altium SDK on ISch_Pin and therefore not surfaced here. - parameters: flat dict of name -> value (cheap lookups). - parameter_styles: list of {name, value, style:{font_id, color, is_hidden, x, y, orientation, justification}} in the same order parameters appear on the symbol. - models: list of the component's implementations (footprint / SPICE / 3D links), each {model_name, model_type, is_current, datafile_links:[{entity_name, file_kind, location}]}. datafile_links is the model's source (the library it resolves from); empty when the footprint binds by name only. Empty list when the part carries no models. - _datasheet_guidance + _datasheet_parts.

lib_audit_stylesA

Bulk visual-style audit across every component in a library.

Walks the focused .SchLib (or one specified by library_path) component-by-component and emits each component's designator style record. Comment / parameter_styles / pins are opt-in via the with_* flags so the default response stays compact: designator alone is ~120 bytes per component, so a 2000-symbol library is ~240 KB without filters.

Filter mode: pass expect_designator_font_id and/or expect_designator_color and the response only contains components whose designator does NOT match the expected style. That makes the audit case (find every symbol that doesn't use Times New Roman 10pt navy) a single round-trip with bounded output.

timeout overrides the bridge default. A 2000-symbol audit with no opt-in flags finishes well under the 10s default; pass a larger value if you flip on with_parameters and the lib has heavy parameter dicts.

Args: library_path: .SchLib path. Defaults to focused doc. with_comment: Include comment style record per component. with_parameters: Include parameter_styles array per component. with_pins: Include pins array per component. expect_designator_font_id: Filter; trim components where designator.font_id equals this value. expect_designator_color: Filter; trim components where designator.color equals this BGR int (e.g. 8388608 for navy / 0x000080 in BGR-packed form). limit: Cap on emitted entries. Default 5000. timeout: Per-call bridge poll timeout override (seconds).

Returns: Dict with library_path, count (emitted), mismatch_count (subset that failed the filter), limit, truncated, filter_applied, and components: list of {name, designator:{...}, mismatched, comment?:{...}, pins?:[...], parameter_styles?:[...]}.

lib_set_label_formatsA

Multi-target label-style writer for SchLib symbols.

Applies SEVERAL label target/style ops to SchLib symbols in one IPC round-trip (there is no singular variant). The library is opened once and walked once; every op is applied to each component in turn. Five sequential per-target writes would collapse into one trip plus one library walk -- which is the dominant cost on large libraries (each individual call runs an IPC, a workspace lookup, a doc-focus check and a CompInfoReader walk).

Each op is a dict describing one label target -- no field has a built-in default, only the ones you supply get written:

{"target": "designator",         # required
 "font_id": <int>,               # optional
 "color":   <int>,               # optional, BGR-packed
 "is_hidden": False,             # optional
 "orientation": <int>,           # optional, 0/90/180/270
 "justification": <int>}         # optional

At least one style field must be set per op. Targets are "designator", "comment", or "parameter:<Name>". Use obj_get_font_id / obj_get_font_spec to resolve the font_id for {name, size, bold, italic} from the active library's font table; that keeps the call neutral to any particular library's style choices.

Args: ops: List of per-target style ops. Must be non-empty. Targets may not contain the wire separators ; or ~~. component_name: When set, applies only to that one component. Omit for bulk-walk across the library. library_path: .SchLib path. Defaults to focused doc. only_mismatched: When True (default) skip labels already matching the target style. Applies globally to every op. limit: Cap on processed components in bulk mode. timeout: Per-call bridge poll timeout override.

Returns: Dict with library_path, scope ("single"|"bulk"), total, limit, truncated, and an ops array each with target, modified, already_compliant, missing_target, failed.

lib_set_mech_layersA

Name, enable and kind the mechanical layers of one library.

WHY THIS EXISTS RATHER THAN THE pcb_* LAYER TOOLS. Those act on whichever board is current, and pointing them at a particular library depends on the focus actually moving. When it does not they operate on the previously focused library while reporting success: a sweep over twenty one libraries returned twenty one identical answers because every call had re-read the same file.

This takes the library by PATH and refuses unless the document that ended up focused is the one asked for. Acting on the wrong library is worse than not acting, because it looks like it worked.

Every change is READ BACK. A layer whose name, enable or kind did not take is reported against that layer rather than folded into an overall pass.

PAIRED KINDS. Any kind ending in Top or Bottom is held by the layer PAIR rather than by either layer, under a shorter name with the side dropped, so "Courtyard Top" is stored as the pair kind "Courtyard". Assigning one therefore needs BOTH sides in the same call: give the partner kind to another layer in layers and the two are joined and the pair given the kind. Single kinds such as "Fab Notes" need no partner.

Pair with lib_run_across to apply the same layer scheme to many libraries in a single call.

Args: layers: One dict per layer. Keys: layer (required, e.g. "Mechanical13"), and any of name, enabled (bool), kind (e.g. "Courtyard Top"). A layer with none of the three is reported as nothing asked for. library_path: The library to edit. Defaults to the focused document, which is only safe for a single library. tidy_pairs: Remove layer pairs no kind justifies. Altium never drops a pair when a kind moves to other layers, so a library reworked more than once accumulates pairs the Layer Stack Manager still shows. A pair survives the tidy only when its two layers carry kinds that are each other's opposite side. Off by default, because it REMOVES pairs and a caller renaming one layer should not have the stack rearranged underneath. timeout: Seconds.

Returns: Dict with library, layers (each: layer, changed, problem), changed, failed, kinds_displaced, pairs_removed, pairs_tidied and pairs_scanned_to.

lib_run_acrossA

Run one library command against several libraries in one call.

Sweeping libraries one at a time costs a round trip and a turn of orchestration per library. This sends the whole sweep as a single request and loops inside Altium.

WHAT THIS DOES NOT SAVE. Opening and saving each library, which is the larger cost and is unavoidable. The saving is the round trips and the waiting between them, which is what makes a twenty library sweep tedious rather than slow.

WHICH ACTIONS WORK. Those that accept a library_path, which is most of the read and batch-edit commands. The footprint EDITING commands (create_footprint, add_footprint_pad, link_3d_model and the rest) act on whichever library is open and cannot be swept, because focusing the document is the work.

FAILURE IS PER LIBRARY. One library that will not open does not abandon the rest, and the reply reports each library separately rather than as a single flag. "17 of 20 worked" collapses into either a false clean or a false failure the moment it becomes one boolean, and the caller cannot tell which library to fix.

Args: action: Library command without its namespace, e.g. "get_components", "batch_rename", "audit_styles". libraries: Full paths. Passed to Altium separated by "|", which cannot occur in a Windows path. params: Parameters shared by every library. Any library_path here is overridden per library. timeout: Seconds for the whole sweep, not per library.

Returns: Dict with results (each: library, success, data, error), succeeded, failed and libraries.

lib_batch_set_paramsA

Batch set parameters on library components.

Each assignment sets one parameter on one component. If the parameter exists it is updated; if not it is created.

Two param_name values are SPECIAL-CASED to component properties rather than parameters:

  • "Description" sets Component.ComponentDescription.

  • "Designator" sets the component's DEFAULT designator (Component.Designator.Text, e.g. "U?" / "R?"). Use this to normalize library designator defaults in bulk (pairs with lib_get_components(with_designator=True)).

Args: assignments: List of dicts with keys: - component_name: Name of the component in the library - param_name: Parameter name (e.g., "Partnumber", "Manufacturer"), or the special "Description" / "Designator" property keys - param_value: Value to set library_path: Path to library (uses active library if not specified)

Returns: Dictionary with counts of updated, created, and failed assignments

lib_batch_renameB

Batch rename components in a schematic library.

Each assignment renames one component from old_name to new_name.

Args: assignments: List of dicts with keys: - old_name: Current name of the component in the library - new_name: New name for the component library_path: Path to library (uses active library if not specified)

Returns: Dictionary with counts of renamed and failed assignments

lib_diff_librariesA

Compare two schematic libraries and report differences.

Returns which components are only in library A, only in B, or shared.

Args: library_a: Full path to the first SchLib file library_b: Full path to the second SchLib file

Returns: Dictionary with only_in_a, only_in_b, common arrays, and count_a, count_b, only_a, only_b, shared counts

lib_add_symbol_arcA

Add an arc to the current library symbol.

Args: x_center: Center X coordinate in mils y_center: Center Y coordinate in mils radius: Arc radius in mils start_angle: Start angle in degrees (0 = right, 90 = up) end_angle: End angle in degrees width: Line width (0=zero, 1=small, 2=medium, 3=large) grid: snap grid (mils); arcs are glyph detail and don't need the 100-mil pin grid (rule 15), so the default is a finer 25 (e.g. inductor coils). Pass 100 for coarse, 0/1 for none.

Returns: Dictionary confirming arc addition

lib_add_symbol_polygonA

Add a polygon (filled shape) to the current library symbol.

Args: vertices: Comma-separated x,y coordinate pairs in mils. Example: "0,0,100,0,100,100,0,100" creates a square with vertices at (0,0), (100,0), (100,100), (0,100). Minimum 3 vertices (6 values) required. grid: snap grid (mils) for the vertices. A filled polygon is glyph detail (e.g. a diode triangle) and doesn't need the 100-mil pin grid (rule 15), so the default is a finer 25; pass 100 for coarse or 0/1 to disable snapping.

Returns: Dictionary confirming polygon addition with vertex count

lib_set_component_descriptionC

Set the description field on a library component.

Args: component_name: Name of the component in the active library description: New description text

Returns: Dictionary confirming the description was set

lib_get_pin_listA

Get all pins of a library component.

NAME THE COMPONENT. Without component_name this reads whatever the SchLib editor currently has selected, so the answer depends on editor state a caller cannot see, and any tool that moves the selection between calls changes what this returns. Passing the name also avoids disturbing the selection, which is what made exporting one symbol affect the next call.

DATASHEET DISCIPLINE: Pin name + electrical_type from the symbol can be wrong, especially on libraries that have been edited by hand or imported from third-party sources. Before relying on a pin's function for any decision, fetch the manufacturer datasheet and verify against its pin-description table. The response carries _datasheet_guidance + _datasheet_parts.

BIG SYMBOLS SPILL TO A FILE ON PURPOSE. A 699-pin module returns more than a conversation can hold, and which half survives is then decided by whichever client happens to be reading. Above inline_limit pins the full array is written as JSON and the reply carries pins_path and a summary instead. That is the better artefact anyway: the file can be diffed against the datasheet's pin table by script, field by field, without any of it passing through the conversation.

Args: component_name: library reference of the symbol to read. Empty falls back to the editor's current component. output_path: write the full pin array here as JSON and return the summary. Forces the file path regardless of size. inline_limit: pin count above which the array is written to a file instead of returned inline. 0 always returns inline, which is what you want only for a small symbol.

Returns: Dictionary with "count", "component" name, and "pins" array. Each pin has: designator, name, electrical_type, x, y, orientation, hidden. Plus _datasheet_guidance + _datasheet_parts.

When spilled: "pins_path" plus "summary" giving pins per
part and the electrical-type distribution, and no "pins".
lib_set_pin_owner_partA

Reassign pins of a multi-part symbol to a sub-part, or to Part Zero.

owner_part_id=0 is Altium's Part Zero: the pin belongs to the package as a whole instead of to one sub-part. That is the documented placement for a multi-part component's supply pins. A sub-part-owned supply pin is redrawn at every instance origin, and where the gate pitch is only twice the pin length those copies land on each other and the netlist merges the two rails.

Changes the library only. Placed instances pick it up on the next Update From Libraries, which produces an ECO.

Args: pin_designators: comma-separated pin numbers, e.g. "3,12". owner_part_id: 0 for Part Zero, or 1..part_count. component_name: library reference of the symbol to edit. Empty falls back to the editor's current component.

Returns: Dictionary with "component", "owner_part_id", the "pins_changed" list and its "count".

lib_export_kicad_symbolA

Export a SchLib symbol to a KiCad .kicad_sym file.

Reads the symbol's pins from the active schematic library and writes a KiCad 6+ S-expression symbol: a body rectangle sized to the pin roots plus one pin per Altium pin (designator -> number, name, electrical type, position, orientation). Coordinates convert mils -> mm. This covers the symbol only; footprint export is separate.

Naming a component SELECTS it in the library, and that outlives the call: exporting is a read, but it leaves the library on the component it exported. Tools called afterwards without a component name act on whatever is current, so a following lib_add_pins would add pins here. The reply reports it in current_component when a name was given.

Args: component_name: LibRef to export. If empty, exports whatever component is currently selected in the SchLib. output_path: Destination .kicad_sym file. Defaults to workspace/.kicad_sym. reference: Reference designator prefix for the symbol (default "U").

Returns: {"output_path", "symbol", "pin_count"} or an error.

lib_export_kicad_footprintA

Export a PcbLib footprint to a KiCad .kicad_mod file.

Reads the footprint's pads from the active (or named) PCB library and writes a KiCad 6+ S-expression footprint: one pad each (designator -> number, position, size, shape, drill, layer set). Through-hole pads (hole > 0) get a drill and the all-copper layer set; SMD pads get the side's Cu/Paste/Mask set. Altium mils convert to mm and the y axis is flipped (Altium y-up -> KiCad y-down). This is the footprint counterpart to lib_export_kicad_symbol.

Args: footprint_name: Footprint to export. If empty, exports the library's current footprint. output_path: Destination .kicad_mod file. Defaults to workspace/.kicad_mod.

Returns: {"output_path", "footprint", "pad_count"} or an error.

lib_copy_componentA

Copy a component WITHIN or BETWEEN schematic libraries.

Replicates the source component (all pins, graphics, parameters) and adds it to the destination library. When dest_library is omitted or equals source_library this behaves as the original same-library duplicate. When dest_library differs, the component is copied across libraries -- the source library is focused for the replicate, then the destination is focused and the clone is added there. The destination ends focused with the new component selected. Save is deferred (call app_save_all to flush).

Args: source_name: lib_ref of the component to copy. new_name: lib_ref for the clone. Defaults to source_name (natural choice for cross-library copies that should keep their identity). source_library: .SchLib path to read from. Defaults to the currently focused document. dest_library: .SchLib path to write to. Omit (or pass the same path as source_library) for a same-library duplicate. overwrite: When True, a component already named new_name in the destination is removed first. Default False returns NAME_EXISTS and changes nothing.

Returns: Dictionary with success, source, new_name, source_library, dest_library, same_library and overwrote flags.

lib_copy_footprintA

Copy one footprint (all pads/primitives) into a PcbLib, optionally renaming.

Footprint analog of lib_copy_component: full-copies the footprint with no delete. source_library defaults to the focused PcbLib; dest_library defaults to the source. A same-library copy requires a different new_name. Errors if new_name already exists in dest unless overwrite.

Args: source_name: the footprint to copy. new_name: name for the copy (default source_name). source_library: source .PcbLib path (default the focused lib). dest_library: destination .PcbLib path (default source). overwrite: replace a same-named footprint already in dest.

Returns: {"success": true, "source": "...", "new_name": "...", "same_library": bool}.

lib_move_componentsA

Move matching components (symbol + params + models) between SchLibs.

Bulk analog of lib_copy_component: copies every matching component from source_schlib to dest_schlib (Replicate carries the whole symbol, its parameters and its models) in one pass, then removes them from source unless delete_from_source is False. This is the piece that makes a full library split a single call.

Provide either names (explicit LibReferences) or name_regex (matched against the source's component names). With names it is one IPC call; with name_regex the source component list is fetched first to resolve the matches, then the move runs. A component already present in dest is skipped unless overwrite=True.

Args: source_schlib: source .SchLib path. dest_schlib: destination .SchLib path. names: explicit list of component LibReferences to move. name_regex: regex matched against source component names when names is not given. delete_from_source: remove moved components from source (default True). overwrite: replace a same-named component already in dest. dry_run: resolve the selection and report it WITHOUT moving anything. Worth doing whenever name_regex is used: the pattern is applied with search, not a full match, so R selects every name containing an R, and the default is to delete them from the source afterwards.

Returns: {"success": true, "moved": N, "skipped": N, "failed": N}, or in dry-run {"dry_run", "resolved", "count", ...} with nothing sent to the bridge.

A failed copy does not delete: the handler counts it in failed and moves on, so the source keeps it. What is NOT recoverable is overwrite=True replacing a different component of the same name in the destination, which is why dry_run reports both flags.

lib_move_footprintsA

Move matching footprints between PcbLibs (the PcbLib analog of move).

Copies every matching footprint (all pads and primitives, via a full copy) from source_pcblib to dest_pcblib, then removes them from source unless delete_from_source is False. The footprint counterpart of lib_move_components; together they split a full library.

Provide either names (explicit footprint names) or name_regex (matched against the source's footprint names). With names it is one IPC call; with name_regex the source footprint list is fetched first. A footprint already present in dest is skipped unless overwrite=True.

Args: source_pcblib: source .PcbLib path. dest_pcblib: destination .PcbLib path. names: explicit list of footprint names to move. name_regex: regex matched against source footprint names when names is not given. delete_from_source: remove moved footprints from source (default True). overwrite: replace a same-named footprint already in dest. dry_run: resolve the selection and report it WITHOUT moving anything. Worth doing whenever name_regex is used: the pattern is applied with search, not a full match, so a short pattern selects far more than intended, and the default is to delete them from the source afterwards.

Returns: {"success": true, "moved": N, "skipped": N, "failed": N}, or in dry-run {"dry_run", "resolved", "count", ...} with nothing sent to the bridge.

lib_split_pin_functionsA

Split slash-delimited pin names into pin function lists.

For the current library symbol, parses each pin whose name is PRIMARY/ALT1/ALT2/... into the pin's function list (the alternate- function popup), leaving PRIMARY as the visible name. Pins without a / are unchanged.

Returns: {"pins_processed": N}.

lib_get_installed_librariesA

List the libraries installed in the Altium environment.

The answer to "what libraries does this installation have?", which no other tool gives: lib_search walks only the SchLibs already open in the workspace, and design_snapshot_inventory has to be handed explicit .SchLib paths. This reads the environment's own list, so it covers .IntLib, .SchLib, .PcbLib, database and query libraries whether or not anything is open.

INSTALLED IS NOT AVAILABLE. Installed libraries are the ones switched on for this environment; available ones are every library it knows about. This returns the installed list and reports the available total beside it, so a library that is present but not switched on shows up as a gap between the two numbers rather than as an absence.

Args: with_counts: True (default) also reports how many components each library holds. That opens every library to count them, so pass False for a fast listing; the count then comes back as -1, meaning not asked rather than empty.

Returns: {"libraries": [{"library_path", "file_name", "library_type", "library_type_ordinal", "component_count"}], "installed_count", "available_count", "counts_included"}. library_type is one of integrated / source / datafile / database / none / query / design_items, or unknown; it reads "unknown" with an ordinal of -1 when a library is installed but missing from the available list, which is a real state and not an error.

lib_install_libraryA

Register a library with the environment's Available Libraries.

Installs an .IntLib / .SchLib / .PcbLib so its parts resolve by lib-ref across the workspace (the step after authoring a library).

Args: library_path: absolute path to the library file.

Returns: {"installed": bool, "library_path": "..."}.

lib_get_vault_nameA

The Workspace name the managed tools would use, read off the design.

Altium writes the Workspace name onto every managed part it places. This reads it from the active sheet, then from the other sheets that are already open; it never opens one. A Workspace has been seen under two names on one design, so a part addressed by kind 4 (VaultName) is preferred and other_kind says when only another kind was found.

Returns: found, vault, other_kind and seen, every kind:name met, separated by |.

lib_find_vault_componentsA

Find the Design Item ID of a managed part by its exact name.

Looks through the managed (Workspace) parts already placed on the sheets Altium has open, and matches name exactly, case ignored, against each part's comment, library reference and Design Item ID. Every match comes back, one per Design Item ID, with the lifecycle state the library manager reports, so two parts sharing a name are both shown rather than the first one being picked.

It does not search the whole Workspace: that needs Altium's EDMS interfaces, whose methods no reference documents, and a call this Altium lacks would stop the bridge. A part not yet used in the design has to be looked up in Altium's Components panel.

Args: name: The comment, library reference or Design Item ID.

Returns: name, count, sheets_searched and matches, each with design_item_id, comment, lib_reference, vault, lib_identifier_kind, revision_guid, lifecycle_state and sheet.

lib_get_vault_componentA

Check one managed (Workspace) component can be placed, without placing it.

Loads the item the way sch_place_vault_components would, reads its identity, and throws it away again; nothing reaches a sheet. The kind asked for is tried first, then the others. The library manager's lookups (library path, lifecycle state, revisions, placement parameters) are reported alongside, as they come back. A part number is not looked up here, so bring the Design Item ID.

Args: design_item_id: The component's Design Item ID. vault: The Workspace name. Usually left out; see lib_get_vault_name. lib_identifier_kind: Altium's TLibIdentifierKind, tried first: 0 Any, 1 NameNoType, 2 NameWithType, 3 FullPath, 4 VaultName (the default). A managed part Altium placed shows the kind and identifier that work, through obj_query on eSchComponent.

Returns: placeable, loader_callable, lib_identifier_kind (the kind that loaded, -1 if none), load_probe (kind:result for each try), the loaded loaded_design_item_id, vault_guid, item_guid and revision_guid, then library_path, display_path, lifecycle_state, has_revisions, revisions, placement_parameters, note and diag (library manager calls that raised).

lib_uninstall_libraryB

Unregister a library from the environment's Available Libraries.

Args: library_path: absolute path to the library file.

Returns: {"uninstalled": bool, "library_path": "..."}.

lib_delete_componentA

Delete one symbol from a schematic library (.SchLib).

Removes the component whose LibReference is component_name and marks the library dirty for deferred save (flushed by app_save_all). Deletes a single named part; if the name is not found the call errors (COMPONENT_NOT_FOUND) rather than silently doing nothing. There is no wildcard mass-delete.

Args: component_name: the component's LibReference (its library name). library_path: optional absolute .SchLib path to target; defaults to the currently focused library document. component_index: alternative to component_name; the integer index from lib_get_components. Use it to delete a part whose name carries unreproducible bytes (embedded quote or control char). Provide exactly one of the two.

Returns: {"success": true, "library_path": "...", "deleted": "..."}.

lib_rename_componentA

Rename one symbol's LibReference in a schematic library (.SchLib).

This is the repair path for a component whose current name carries bytes a caller cannot reproduce (an embedded double-quote, or a control char left by a broken import). Address it by component_index from lib_get_components, give it a clean new_name, and every name-based tool can reach it again.

The component is removed and re-added under the new name so the library's internal by-name index is rebuilt. Errors with NAME_EXISTS if a different component already uses new_name. Marks the library dirty for deferred save.

Args: new_name: the clean LibReference to write. component_name: current LibReference of the target. Provide this or component_index. library_path: optional absolute .SchLib path; defaults to the focused library. component_index: the integer index from lib_get_components, the name-free way to pick the target. Provide this or component_name.

Returns: {"success": true, "library_path": "...", "old_name": "...", "new_name": "..."}.

lib_delete_footprint_primitivesA

Delete primitives inside ONE footprint of a PCB library.

THE ONLY LIBRARY-SCOPED PRIMITIVE DELETE. obj_delete and pcb_delete_object both resolve a BOARD, and when no board is focused that lookup opens the first PcbDoc any open project holds. Aimed at a library they do not fail; they remove primitives from a board you never named, and the reply does not say which one. This is the tool for editing a footprint.

SCOPED THREE WAYS, all of which must agree before anything is removed: the library by path, the footprint by name, and the object type or layer. The library is verified AFTER it is brought to the front, so a delete aimed at one that never focused is refused rather than landing in whichever did.

PADS ARE EXCLUDED unless include_pads. Deleting a pad changes the part's connectivity rather than its drawing, and clearing graphics off a layer should not quietly cost you the pinout.

THE FOOTPRINT ON SCREEN IS EDITED FROM ANOTHER. The footprint the editor shows keeps its primitives on the library board, which is written back into it at the next switch, so removing from it in place came undone. When the target is on screen, another footprint is made current for the delete (a temporary one in a single-footprint library, removed afterwards) and the target is shown again at the end; was_current says this happened.

Args: footprint_name: The footprint to edit. Required: deleting from whichever footprint the editor happens to show is the mistake this avoids. object_type: PCB object type, e.g. "track", "arc", "text", "region", "fill". Give this or layer. layer: Restrict to one layer, e.g. "TopOverlay", or a name the library's stack gives it, such as "Top Assembly". A name the stack does not have is refused, with the names it does have. library_path: The .PcbLib. Defaults to the focused document. include_pads: Allow pads to be removed. Off by default. confirm: Required. Read the footprint first with lib_probe_footprint and pass True once the filters are what you mean.

Returns: Dict with library, footprint, removed, examined, layers, pads_included and was_current.

lib_move_bodies_to_layerA

Put the 3D bodies of the focused PCB library on one mechanical layer.

Libraries put together from several sources carry their bodies on whatever layer each source used, so one footprint's model shows with a layer turned on and the next one's never does. Every footprint, or only the named ones, has each body moved; a body already on the layer is counted and left alone. Nothing is added or removed.

Args: layer: The mechanical layer, by number ("Mechanical13") or by the name the library's stack gives it. Anything else is refused, as Altium's body dialog only offers mechanical layers. footprint_names: Only these footprints. Empty means every footprint in the library; names no footprint has come back in not_found.

Does NOT save the library. A mechanical layer the library has not enabled is accepted, with layer_enabled false and a warning, since Altium does not draw what is on it.

Returns: Dict with layer, layer_enabled, footprints, bodies, moved, already_on_layer, footprints_without_bodies, not_found, and items ({name, moved, from} for each footprint that changed).

lib_redraw_assemblyA

Draw the assembly layer of the focused PCB library again from each footprint's own geometry: a rectangle round the part and a .Designator in its middle.

Libraries collected over the years carry the assembly drawing on the wrong layer, from another package, or not at all. With replace (the default) each footprint's tracks, arcs, texts, fills and regions on layer are cleared first; 3D bodies and pads on that layer are never touched.

Args: layer: The mechanical layer the assembly drawing goes on. footprint_names: Only these footprints. Empty means all. outline_from: What the rectangle goes round. body (the 3D bodies), pads (their copper, without mask expansion), courtyard (whatever is drawn on courtyard_layer), or auto (bodies, then the courtyard when courtyard_layer is given, then pads). A footprint with none of them is skipped and named in skipped. courtyard_layer: The layer the courtyard is drawn on. It cannot be layer itself with replace, which would clear the courtyard. width: Line width. Default 4 mil (0.1 mm). designator: Place a .Designator text in the middle, turned upright in a box taller than wide. text_height: Text height. 0 sizes it to a third of the box's short side, kept short enough for a four-character designator to fit along the long side, between 0.2 and 1 mm. replace: Clear what is on the layer first. With False the new drawing goes beside what is there. confirm: Required with replace, since it clears that layer in every footprint it redraws. units: "mil" (default) or "mm" for width and text_height.

The footprint on screen is shown again at the end. Does NOT save the library. A mechanical layer the library has not enabled is accepted, with layer_enabled false and a warning, since Altium does not draw what is on it.

Returns: Dict with layer, layer_enabled, redrawn, cleared, skipped, not_found, and items ({name, outline_from, cleared, width_mm, height_mm} per footprint drawn).

lib_delete_footprintA

Delete one footprint from a PCB library (.PcbLib).

Finds the footprint by name, removes and deregisters it, and MARKS the .PcbLib dirty. It does NOT write to disk: this bridge defers saves, and app_save_all (or proj_save on the LibPkg) is what flushes them. Reported: this said it saved, the footprint was still in the file and the timestamp unchanged until an explicit save.

Deletes a single named footprint; if the name is not found the call errors (FOOTPRINT_NOT_FOUND). No wildcard mass-delete.

Args: footprint_name: the footprint's name in the library. library_path: optional absolute .PcbLib path to target; defaults to the currently focused library document.

Returns: {"success": true, "library_path": "...", "deleted": "..."}.

lib_set_model_nameA

Set a model's model_name (footprint reference) on a SchLib symbol.

Rewrites the ModelName of one of the component's implementations. If model_name is given, the model whose current ModelName matches is targeted; otherwise the current (or first) model is used.

Args: component_name: the component's LibReference. new_model_name: the new model_name to write. model_name: optional current model_name to target a specific one. library_path: optional .SchLib path; defaults to the focused lib.

Returns: {"success": true, "component": "...", "new_model_name": "..."}.

lib_set_model_sourceA

Write the datafile-link Location (source library) on a footprint model.

A footprint whose datafile link has an empty Location resolves in the SchLib editor by name but cannot embed into a self-contained compiled library, so the footprint goes missing in the used/compiled output even though the package's own compile reports no error. This writes Location so the model is embeddable. Location is set in place (never through AddDataFileLink with a path, which wedges AD26). Targets PCBLIB models matching model_name, or all PCBLIB models when it is empty.

IMPORTANT: get the exact source_library string from a known-good model that already embeds correctly. Read it via lib_get_component_details, whose models array now shows each model's location and use_component_library. This tool writes exactly what you pass; it does not infer the format.

Args: component_name: the component's LibReference. source_library: the source-library reference written into Location. model_name: optional footprint model name to target one model. use_component_library: optional; sets the embed-vs-search flag. library_path: optional .SchLib path; defaults to the focused lib.

Returns: {"success": true, "component": "...", "source_library": "...", "models_updated": N}.

lib_remove_modelA

Remove models (implementations) from a SchLib component by name.

Removes every implementation whose ModelName equals model_name. With keep_one=True it keeps the first match and removes the rest, which deletes duplicate model links while preserving one. Errors if the component is not found; returns removed=0 if no model matches.

Args: component_name: the component's LibReference. model_name: the ModelName of the model(s) to remove. keep_one: keep the first match and remove the rest (dedup). library_path: optional .SchLib path; defaults to the focused lib.

Returns: {"success": true, "model_name": "...", "removed": N, "kept_one": bool}.

lib_rename_footprintA

Rename a footprint in a PCB library (.PcbLib).

Renames the footprint whose name is footprint_name to new_name. Errors if footprint_name is not found or new_name already exists in the library.

MARKS the .PcbLib dirty; it does not write. Call app_save_all to flush, and check what it reports actually reached disk.

Args: footprint_name: the current footprint name. new_name: the new footprint name. library_path: optional .PcbLib path; defaults to the focused lib.

Returns: {"success": true, "footprint": "...", "new_name": "..."}.

lib_probe_footprintA

Read-only dump of a PcbLib footprint's name-bearing fields.

Use this to locate where an old name persists after a rename. On a PcbLib footprint, Name and Pattern are the SAME property (renaming Name writes both), and the footprint's only metadata is Name, Description, Height. So a leftover old name lives in a child primitive: this returns every text primitive's object_id and text, plus name/description/ height_mils/primitive_count. Nothing is written.

Args: footprint_name: the footprint name. library_path: optional .PcbLib path; defaults to the focused lib.

Returns: {"success": true, "footprint": "...", "description": "...", "height_mils": N, "primitive_count": N, "texts": [{"object_id": N, "text": "..."}]}.

lib_get_pad_geometryA

Full-precision pad geometry of one PcbLib footprint, in mm.

The measurement half of the datasheet land-pattern audit. Unlike the integer-mil dump behind lib_audit_footprint_policies, every dimension here is a float in millimetres (a 0.65 mm pitch loses 10 um to integer-mil rounding, which a tolerance comparison against the datasheet cannot afford). Coordinates are relative to the footprint's own origin.

Args: footprint_name: the footprint name in the library. library_path: optional .PcbLib path; defaults to the focused library.

Returns: {name, description, library_path, pad_count, pads: [{ name, x_mm, y_mm, copper_x_mm, copper_y_mm, w_mm, h_mm, shape, corner_pct, rotation, hole_rotation, hole_mm, hole_w_mm, hole_type, slot_aligned (slots only), plated, layer, paste_expansion_mm, paste_expansion_source, paste_expansion_state, mask_expansion_mm, mask_bottom_expansion_mm, mask_separate, mask_expansion_source, mask_expansion_state}]}. mask_expansion_mm is the TOP side when mask_separate is true and both sides when it is false; mask_bottom_expansion_mm is the stored bottom value, which takes effect once the sides are unlinked even while it is not in use. x_mm/y_mm are the pad's origin; copper_x_mm/copper_y_mm are the centre of its copper, which differs on a custom pad. hole_rotation is relative to the pad, so a slot lines up when the copper's and the hole's long axes agree in the pad's frame, which slot_aligned reports. *_expansion_source is 'manual' when the pad overrides the design rule, 'rule' otherwise (value 0 in that case: the rule's value lives in the design rules, not on the pad); *_expansion_state is the cache's own manual, valid or invalid.

lib_set_pad_propertiesA

Change pads that already exist in a PcbLib footprint.

Every pad named pad_name in the footprint is changed, and only the fields you pass are written, so setting plating leaves the shape alone. Each pad is read back afterwards and the reply reports what it holds, not what was asked.

A mask or paste expansion is written with the pad's manual flag set: without it Altium ignores the value and follows the board's rule, and a library has no rules. hole_width makes the hole a slot; hole_rotation is relative to the pad, so a slot lines up when its long axis matches the copper's in the pad's frame (see slot_aligned in lib_get_pad_geometry). On a pad with a per-layer stack only the top size is written, and note says so.

THE MASK HAS TWO SIDES. A pad keeps a top and a bottom solder mask expansion; with the sides linked the top value serves both, but the bottom value is still stored and takes effect the moment the sides are unlinked. So mask_expansion writes both sides unless mask_bottom_expansion is given too, which writes the bottom and unlinks the sides. mask_separate links or unlinks them explicitly. The reply reads back all three.

Args: footprint_name: The footprint holding the pad. pad_name: The pad's designator. library_path: Optional .PcbLib path; defaults to the focused library. units: "mil" (default) or "mm", for every length here. x_size, y_size: Copper size. shape: "round", "rectangular" or "octagonal". rotation: Pad rotation in degrees. hole_size: Hole diameter, 0 for a surface-mount pad. hole_width: Slot length; makes the hole a slot. hole_rotation: Slot rotation relative to the pad, in degrees. plated: Whether the hole is plated. paste_expansion, mask_expansion: Expansions, set as manual. mask_expansion is the top side, and the bottom too unless mask_bottom_expansion is given. mask_bottom_expansion: The bottom side's mask expansion; the sides are unlinked. mask_separate: Link (False) or unlink (True) the two sides.

Returns: pads_changed, pads_with_a_failed_write, note and pads, each as read back (position, size, rotation, hole, slot, plating, expansion states, mask_expansion_mm, mask_bottom_expansion_mm, mask_separate, all_writes_ran).

lib_get_footprint_primitivesA

Every non-pad primitive of one PcbLib footprint, with where it is.

Tracks, arcs, regions, fills, texts and vias, each with its layer (the name the library's stack gives it, plus layer_id), in mm from the footprint's origin: the frame the lib_add_footprint_* writers take. Pads are in lib_get_pad_geometry. Read-only. Each primitive is listed once. Altium's own iterator yields one just added to the footprint on screen twice until that footprint is shown again; the footprint writers here show it again after each write, and this read skips a repeat either way.

Args: footprint_name: The footprint name in the library. library_path: Optional .PcbLib path; defaults to the focused library.

Returns: {name, library_path, height_mm, bodies (a count), body_layers (the layer of each body), tracks [{layer, layer_id, x1, y1, x2, y2, width}], arcs [{layer, layer_id, xc, yc, r, start, end, width}], regions and fills [{layer, layer_id, x1, y1, x2, y2, area (regions)}], texts [{text, layer, layer_id, x, y, size, rotation, x1, y1, x2, y2}], vias [{x, y, diameter, hole}]}. A text's box is Altium's cached one and can lag an edit made this session.

lib_audit_footprint_vs_datasheetA

Audit a footprint against the manufacturer's land pattern.

Deterministic comparison of the REAL pad geometry (read live from the .PcbLib) against a land-pattern spec transcribed from the manufacturer datasheet: pad count, every pad's position / size / shape / drill, the numbering sequence, pitch (implied by positions), the thermal pad, and its paste policy. Alignment is automatic (centroid + best of the four 90-degree rotations), so library origin and rotation conventions do not produce false errors; a MIRRORED pattern is deliberately not compensated and reports as position/sequence errors.

DATASHEET DISCIPLINE: the spec must be transcribed from the manufacturer datasheet fetched in this conversation, and spec.source must cite it (url + figure/page + part number). Symbol metadata, distributor drawings, or memory of a package are not acceptable sources. There are no built-in package tables here, and that is intentional.

The spec (JSON object or string): source: {datasheet_url, reference, part_number} REQUIRED pads: [{name, x, y, w, h, shape?, hole?}] explicit, mm dual_row: {count, pitch, span, pad_w, pad_h, shape?} quad: {count, pitch, span_x, span_y, pad_w, pad_h, shape?} thermal_pad: {name, x, y, w, h, shape?} thermal_paste: 'full' | 'windowed' position_tol / size_tol: mm, default 0.05

dual_row expands with the SOIC/SOP convention (pin 1 top-left, CCW); quad with the QFP/QFN convention (pin 1 top of left side, CCW). For any other numbering give explicit pads. pad_w has two meanings, kept for existing callers: in dual_row it is the pad's X size, ACROSS the row; in quad it runs ALONG the row.

Returns: {ok, rotation_applied_deg, findings: [{check, severity, message, pad?, expected?, actual?}]} plus the footprint geometry under 'footprint' for reference. ok is true only with zero error-severity findings.

lib_audit_footprint_vs_packageA

Audit an existing PcbLib footprint against its package drawing.

Where lib_audit_footprint_vs_datasheet compares pads with a land pattern typed in, this takes the PACKAGE (the same PackageSpec as lib_create_footprint_from_package) and checks the footprint is right for it. Reads only.

Checked: pad count, numbering and pin 1 (a mirror-image footprint, the bottom-view mistake, is named as such); pitch exactly; row span, land sizes and land centres against the IPC-7351B pattern within the deviations measured between real vendor patterns and IPC (a footprint drawn to the vendor's own pattern passes); every land covering the centre of its terminal; holes against the leads; paste on an exposed pad (closed opening plus windows covering 50 to 80 %) and on every land; manual solder mask settings; origin at the package centroid; and, from the footprint's other primitives, a courtyard that encloses the lands and the body, silkscreen off the copper, an assembly outline that follows the body, and the footprint height against the package's maximum. A turned footprint is compared after turning it back. What could not be read is listed under not_examined.

Run footprint_check_package on the package first: an audit against a misread package passes a footprint built from the same misread.

Args: footprint_name: the footprint in the library. package: the PackageSpec as an object or JSON text. density: L, N or M, the IPC pattern compared against. library_path: optional .PcbLib path; default the focused one. assembly_layer, courtyard_layer: the mechanical layers for those roles; empty picks them by the library's layer kind ("Assembly Top" / "Courtyard Top"), then by name.

Returns: {ok, errors, warnings, findings: [{check, severity, message, pad?, expected?, actual?}], examined, not_examined, alignment, metrics, notes, package_check}. ok is true with no error-severity finding.

lib_easyeda_searchA

Search LCSC / EasyEDA for parts by MPN or description.

EXPECT THIS TO FAIL, and NOT because of credentials. Checked against the live service: the search route answers 404/403 with an HTML error page and no auth challenge, component fetch on the SAME host still works with no credentials, LCSC's own search API returns an error body, and the LCSC results page is rendered client-side (fetching it yields zero part numbers). There is no endpoint left to log in to, so no cookie, token or account will restore this.

Search is therefore a BROWSER task, which also keeps it per-user: each person searches LCSC in their own signed-in browser, and no shared credential is ever stored by this server. Take the part number from there and import by id, which is unaffected: lib_easyeda_import(lcsc_id="C1234", ...).

Online. Returns candidates only, no geometry: pick an lcsc_id and pass it to lib_easyeda_import. HTTPS-only with a host allowlist; override endpoints with EASYEDA_API_BASE.

Args: query: MPN, keyword, or description fragment. limit: max candidates (default 20).

Returns: {"ok": true, "count": N, "results": [{lcsc_id, mpn, manufacturer, package, description}]}.

lib_kicad_importA

Convert a KiCad .kicad_sym / .kicad_mod into an Altium plan.

Closes the gap that made KiCad-format part sources useless on the Altium side: this server could export Altium to KiCad but had no path back, so a hit from a KiCad registry or from the local KiCad libraries (see part_search) was a dead end.

Parses into the SAME neutral model the EasyEDA importer uses, so the Altium plan is produced by one shared emitter rather than a second copy that would drift from it.

Give symbol_path, footprint_path, or both. Use symbol_name to pick one symbol out of a multi-symbol library (the first is used otherwise).

MULTI-PART COMPONENTS: a quad gate or dual op-amp converts in ONE call to a real Altium multi-part symbol (part_count plus per-pin owner_part_id), not to N symbols to merge by hand. Set unit to a positive number to convert only that sub-part instead. Units are never merged into a flat symbol: they share coordinates by design, so a merged symbol has every unit's pins stacked on the same points and still looks converted.

HIDDEN PINS: kept, and kept hidden. A hidden pin is electrically real (this is how symbols carry supply rails and no-connects), so dropping it would lose a pin while showing it would clutter the symbol.

DERIVED SYMBOLS: over half of KiCad's standard entries carry no geometry of their own and inherit it from a parent. That link is followed, and warnings says where the geometry came from.

target="altium": returns an ORDERED PLAN of this server's own library tools, exactly like lib_easyeda_import. Nothing is written to Altium; review the plan, then execute the steps.

target="inspect": parse only, return a summary and warnings. Use this first on an unfamiliar library.

DATASHEET DISCIPLINE: a converted footprint is somebody else's drawing, not a verified land pattern. Audit it against the manufacturer document with lib_audit_footprint_vs_datasheet before trusting it.

Returns: {"ok": ..., "component": {...}, "warnings": [...]} plus "steps"/"summary" for target="altium".

lib_easyeda_importA

Convert an EasyEDA / LCSC part to KiCad files or an Altium plan.

Independent implementation from EasyEDA's published format spec, so symbol and footprint geometry, pads, drills, silkscreen and metadata all convert without any third-party converter.

Source: lcsc_id fetches online (e.g. "C7420"), or payload_path reads a saved component JSON, which keeps the whole conversion offline and reproducible.

target="kicad": writes <name>.kicad_sym and <name>.kicad_mod into output_dir and returns their paths. With fetch_3d (default on) the part's 3D model is fetched and converted to <name>.wrl, which the footprint then references. Altium needs STEP and cannot use VRML, so this is KiCad only.

target="altium": returns an ORDERED PLAN of this server's own library tools (lib_create_symbol, lib_add_pins, lib_create_footprint, lib_add_footprint_pads, lib_link_footprint, ...) that recreates the part in schlib_path / pcblib_path. Nothing is written to Altium: review the plan, then execute the steps (tool_invoke drives them directly). Altium's binary library formats are never synthesized, the bridge authoring API is used instead.

target="inspect": parse only, return the component summary and warnings. Use this first on an unfamiliar part.

DATASHEET DISCIPLINE: an imported footprint is a vendor drawing, not ground truth. Audit it with lib_audit_footprint_vs_datasheet against the manufacturer land pattern before trusting it. Polygon pads and slotted holes have no faithful equivalent and are reported in warnings.

Returns: {"ok": ..., "component": {...}, "warnings": [...]} plus "files" (kicad) or "steps"/"summary" (altium).

lib_clear_source_libraryA

Unpin a SchLib's symbols from their source-library provenance.

Library-side sibling of sch_clear_source_library. Symbols copied in from another library (a vendor pack, a stock library like the built-in miscellaneous-devices set) carry three provenance fields pointing at their ORIGIN: SourceLibraryName, TargetFileName, and DesignItemId. Left stale, every placement from your library re-links against a library that no longer exists on the machine and lands in the state.

Per matching component: clears SourceLibraryName, resets TargetFileName to '*', and syncs DesignItemId to the LibReference (each independently switchable). This is the minimal fast path of what lib_normalize_implementations does inside its full model sweep, when you only need the provenance cleaned, use this. Deferred save: flush with app_save_all.

Args: library_path: absolute .SchLib path; empty = focused library. component_names: restrict to these LibReferences; None/empty = every component in the library. clear_target_file_name: reset TargetFileName to '*'. sync_design_item_id: set DesignItemId = LibReference where they differ.

Returns: {"library_path": ..., "total": inspected, "cleared_source_library": n, "cleared_target_file_name": n, "synced_design_item_id": n}.

lib_normalize_implementationsA

Clean up every component's models in a SchLib for a self-contained package.

Whole-library sweep. Per component it: clears the component-level provenance (SourceLibraryName, TargetFileName); collapses duplicate models, keeping ONE per (model_type, model_name) IN PLACE so the kept model's parameters, MapAsString pin-map, and datafile link are all preserved; re-flags the survivor current if any duplicate was; optionally renames a model's name and its datafile entity via rename_map; and repairs any footprint (PCBLIB) left with no datafile link. It does NOT destroy and rebuild implementations, so nothing on a model is lost.

With source_library set, it also writes that value into the Location of every PCBLIB model's datafile link, turning a name-only library (footprints resolve but do not embed) into an embeddable one in the same pass. Existing Locations are only overwritten when source_library is given; they are never blanked. Get the exact string from a known-good model first (see lib_set_model_source).

Note: it preserves what is present; it cannot resurrect data a prior broken run already dropped. If a library was damaged by an earlier version, restore the backup and run this on it.

If you are also renaming footprint entities, run lib_rename_footprint on the PcbLib first, then pass the same {old: new} as rename_map so symbol and footprint stay matched.

Args: library_path: optional .SchLib path; defaults to the focused lib. rename_map: optional {old_model_name: new_model_name} applied to the kept model's name and matching datafile entity. dedupe_only: skip renames; only collapse duplicates + clear source. source_library: optional; when set, written into every PCBLIB model's datafile Location so footprints embed on compile. use_component_library: optional; sets the embed-vs-search flag on the models whose Location is written.

Returns: {"success": true, "components_touched": N, "duplicates_removed": N, "sources_cleared": N, "links_repaired": N, "sources_set": N}.

lib_inspect_cse_zipA

Identify the library members of a Component Search Engine zip.

Stage 5 of the autonomous flow (library readiness, reference/autonomy-roadmap.md): a SamacSys / Component Search Engine download carries the symbol, footprint and 3D model for an MPN in one zip. This tool reads the archive WITHOUT extracting anything: which member is the .SchLib, which the .PcbLib, which the STEP model, the best-effort MPN, and any members attempting path traversal (suspicious). Inspect first, then stage the files with lib_extract_cse_zip. Pure Python, no Altium.

Args: zip_path: Absolute path to the downloaded zip.

Returns: {"ok": True, "mpn", "schlib", "pcblib", "step", "extras", "suspicious"} (member names; None when absent) or {"ok": False, "reason": "..."} when the file is missing, not a zip, or contains no .SchLib/.PcbLib.

lib_extract_cse_zipA

Extract a Component Search Engine zip and build its install plan.

Stage 5 of the autonomous flow (library readiness, reference/autonomy-roadmap.md): stages the recognized members (.SchLib / .PcbLib / STEP) flattened into dest_dir and returns an ordered install_plan whose steps are exact lib_install_library / lib_link_footprint / lib_link_3d_model parameter dicts -- dispatch them in order to make the part placeable. Any archive member with an absolute path, drive letter, or .. segment rejects the WHOLE archive before anything is written. Extraction is pure Python; only the install plan touches Altium when dispatched.

Args: zip_path: Absolute path to the downloaded zip. dest_dir: Where to stage the files. Default: <workspace>/cse_imports/<zip stem>.

Returns: {"ok": True, "mpn", "files": [abs paths], "extracted": {"schlib"/"pcblib"/"step": abs path or None}, "install_plan": [{"tool", "params"}]} or {"ok": False, "reason": "..."}.

obj_queryA

Query schematic OR PCB objects and read their properties.

Iterates objects of the given type, optionally filtering by property values, and returns the requested properties for each matching object.

Args: object_type: Altium object type constant. Schematic: "eNetLabel", "ePort", "ePowerObject", "eSchComponent", "eWire", "eBus", "eBusEntry", "eParameter", "ePin", "eLabel", "eLine", "eRectangle", "eSheetSymbol", "eSheetEntry", "eNoERC", "eJunction" PCB: "eTrackObject", "ePadObject", "eViaObject", "eComponentObject", "eArcObject", "eFillObject", "eTextObject", "eRuleObject", "eDimensionObject", "eConnectionObject" (the ratsnest: one line per unrouted connection, with X1, Y1, X2, Y2, Layer1, Layer2 and Net) properties: Comma-separated property names to return.

    SCHEMATIC objects use the dotted spelling:
    "Text,Location.X,Location.Y" for net labels
    "Designator,Name,ConnectionX,ConnectionY" for pins.
    ConnectionX/ConnectionY are the pin's ELECTRICAL end --
    the point a wire or net label must sit on to attach.
    Location is the BODY-side root and is NOT the connection
    point; deriving the end by hand from Orientation and
    PinLength is a common source of wrong conclusions about
    what is wired.
    "Designator.Text,Comment.Text,LibReference" for components
    A managed (Workspace) component also reads, but does not
    write, "LibIdentifierKind,LibraryIdentifier,DesignItemId,
    VaultGUID,ItemGUID,RevisionGUID,SymbolReference,
    SymbolVaultGUID,SymbolItemGUID,SymbolRevisionGUID". Read
    off a part Altium placed, they are the values the managed
    tools need.

    PCB PRIMITIVES DO NOT. They are a separate, flat set,
    and mixing the two is the single commonest mistake here:
    a caller who used Designator.Text infers a dotted rule,
    writes Net.Name on a track, and gets an error naming the
    right spelling. Net is the one that catches people, and
    it is plain "Net":
    "Net,Layer,Width" for tracks
    "Net,Layer,HoleSize,Size" for vias
    "Designator,Comment,Pattern,Rotation,X,Y" for components
    "Text,Height,StrokeWidth,IsDesignator" for texts
    Full set: ObjectId, X, Y, Layer, Descriptor, Selected,
    Net, X1, Y1, X2, Y2, Width, Radius, StartAngle,
    EndAngle, XCenter, YCenter, HoleSize, Size, TopShape,
    TopXSize, TopYSize, Rotation, Name, Text, Pattern,
    Designator, Comment, SourceDesignator, Kind,
    RemoveDead, RemoveNarrowNecks, RemoveIslandsByArea,
    StandoffHeight, OverallHeight, BodyOpacity3D, InComponent, Component,
    InPolygon, IsKeepout, Locked, Layer1, Layer2,
    IsRedundant, Mode, Height, StrokeWidth, IsDesignator,
    IsComment, UseTTFonts.

    On a text, Height and StrokeWidth are writable in mils
    (Altium's own names, Size and Width, answer the same).
    On a via, Size and HoleSize are writable; a write that
    would leave the hole as large as the pad is refused, so
    give Size first to grow a via and HoleSize first to
    shrink it. Lengths read back in decimal mils.
    On a pad, Name and Designator both read and write its
    designator; an empty one is refused.
    On a 3D body, BodyOpacity3D reads and
    writes 0 (clear) to 1 (solid), not a percentage.
    IsDesignator and IsComment pick out component labels:
    filter "IsDesignator=true" for every designator.

    A board type walks EVERY primitive of that type, a
    footprint's own tracks and a hatched polygon's tracks
    with the routing, on the same copper layers. Filter
    "InComponent=false|InPolygon=false" keeps to free copper;
    Component is the owning footprint's designator.

    An unrecognised PCB property is REFUSED and the reply
    lists the valid ones. It used to come back empty, which
    is indistinguishable from a property that is genuinely
    blank, and that ambiguity has three times been read as
    "the bridge cannot see this data".

    A track on a mechanical layer really does have no net,
    so filter to copper before concluding anything from an
    empty Net: filter="Layer=TopLayer".
scope: Document scope:
    "active_doc", current sheet only (default)
    "project", all SCH sheets in focused project
    "doc:C:\path\to\Sheet.SchDoc", specific sheet by path (no focus change)
    "project:C:\path\to\Project.PrjPcb", specific project by path
    "lib_component:NAME", a named symbol in the active SchLib
    "lib_component:NAME@3", part 3 of a MULTI-PART symbol.
        A SchLib iterator yields one part at a time, so
        without the suffix only part 1 is visible and a pin
        on part 3 reports matched:0. Query each part in
        turn to cover the whole component. The suffix is
        read from the RIGHT, so a LibRef containing '@'
        still works.
        Ask for the `OwnerPartId` property to see which
        part a returned primitive belongs to (0 means
        shared across every part).
filter: Pipe-separated property=value conditions (AND logic), e.g.:
    "Text=VCC", match net labels with Text equal to VCC
    "Designator.Text=R1", match component with designator R1
    "" (empty), match all objects of the type
    On a PCB type a length (X, Y, X1, Y1, X2, Y2, Width, Size,
    HoleSize, ...) matches as a number in mils, to 0.00005
    mil, with '.' or ',' as the decimal point:
    "X2=3172.6356" and "X2=3172,63560" are the same filter.
limit: Maximum number of objects to return (0 = unlimited)

Returns: Dictionary with "objects" array and "count"

obj_explain_pinA

Explain WHY a schematic pin is on the net it is on.

proj_get_nets gives the verdict but never the evidence, so a surprising net assignment can only be debugged by reconstructing Altium's connectivity by hand from raw geometry. This returns the evidence directly.

Reports the pin's ROOT (Pin.Location, body side) and its CONNECTION point (Location + PinLength along Orientation), then lists every schematic object sitting on each point.

Reading the result:

  • at_connection is what actually drives the pin's net. Two net labels with different text there is a short between two named nets, and the usual symptom is one of them reporting zero pins.

  • at_root is electrically INERT. A net label there is the classic "sheet looks wired but the pin floats on an auto-net" bug.

  • An empty at_connection with a populated at_root means the label must move by pin_length_mils along the pin's orientation; the target is given as connect_x_mils / connect_y_mils.

Hits are filtered by the object's Location (or a pin's electrical end, or a wire vertex), not by bounding box. Altium's AddFilter_Area matches text boxes, which extend hundreds of mils from a net label; without the Location check this tool reports labels that are merely nearby.

Args: designator: Component designator, e.g. "U10". pin: Pin NUMBER (not pin name), e.g. "5".

Returns: Dict with {designator, pin, pin_name, sheet, orientation, pin_length_mils, root_x_mils, root_y_mils, connect_x_mils, connect_y_mils, compiled_net, at_connection[], at_root[]}. Each object entry carries {kind, text, x_mils, y_mils} where kind is one of net_label, power_object, port, wire, junction, pin, other. compiled_net is the pin's net from the compiled netlist, so geometry and compile can be compared in one response.

obj_modifyA

Apply ONE set of property values to every object matching ONE filter.

IMPORTANT, if every target object needs a DIFFERENT value (move 10 pins to 10 different positions, rename 5 nets to 5 different names, set distinct designators per component), use obj_batch_modify instead. Each call to this tool is a full LLM round-trip; doing that in a loop is the single biggest wall-time cost in the server. One obj_batch_modify with a list of operations does the same work in one turn.

Use this tool when the SAME set string applies to every match (e.g., "set every 10k resistor's Tolerance to 1%").

Args: object_type: Altium object type constant (see obj_query) set: Pipe-separated property=value assignments to apply, e.g.: "Text=NEW_NAME", set Text property "Location.X=100|Location.Y=200", set multiple properties "Height=31.5|StrokeWidth=3.94", a board text's size (lengths on a board are decimal mils; a value that is not a number is reported failed, never written as 0) scope: Document scope: "active_doc", current sheet only (default) "project", all SCH sheets in focused project "doc:C:\path\to\Sheet.SchDoc", specific sheet by path (no focus change) "lib_component:NAME", a named symbol in the active SchLib -- selects that component first, so there is no need for a separate lib_set_current_component call filter: Pipe-separated property=value conditions (AND logic)

Returns: Dictionary with "matched" count and "sheets_processed"

Example - rename a net across all sheets (one value fits all matches): obj_modify( object_type="eNetLabel", scope="project", filter="Text=OLD_NET", set="Text=NEW_NET" ) Example - modify a specific sheet without switching: obj_modify( object_type="eParameter", scope="doc:C:\path\MODULE_A.SchDoc", filter="Name=Title", set="Text=Module A" ) Example - edit a pin inside one SchLib symbol (no lib_set_current_component): obj_modify( object_type="ePin", scope="lib_component:R_0402", filter="Designator=1", set="Name=A" )

obj_createA

Create and place a schematic object.

If you are creating more than one object, use obj_batch_create instead, one IPC round-trip for the whole set vs one LLM turn per object.

Args: object_type: Altium object type constant (see obj_query) properties: Pipe-separated property=value assignments, e.g.: "Text=MY_NET|Location.X=100|Location.Y=200" container: "document" (place on active schematic) or "component" (add to current library component)

Returns: Dictionary confirming creation

obj_deleteA

Find and delete schematic or PCB objects.

For several scope/type/filter sets at once, use obj_batch_delete , one IPC round-trip vs one LLM turn per delete.

A PCB type (eTrackObject, eViaObject, ...) deletes from the active board, and matches a footprint's own primitives and a polygon's hatching too unless the filter says "InComponent=false" and "InPolygon=false". To take up routing, pcb_unroute does that with the right exclusions built in.

Args: object_type: Altium object type constant (see obj_query) scope: "active_doc", "project", "doc:PATH", or "lib_component:NAME" (a named symbol in the active SchLib). A PCB type takes only "active_doc". filter: Pipe-separated property=value conditions (AND logic). WARNING: empty filter deletes ALL objects of the type. confirm_delete_all: Must be True to delete all objects when filter is empty.

A filter that is not Name=Value pairs (an Altium query expression such as OnLayer('TopLayer') Or ..., or a bare name) is refused, and nothing is deleted.

Returns: Dictionary with "matched" count (number deleted)

obj_get_font_specA

Get font properties for a given font ID.

Reads the Altium font table to retrieve the full font specification (size, name, bold, italic, etc.) for a font ID obtained from an object's FontId property.

Args: font_id: Font ID from an object's FontId property

Returns: Dictionary with font_id, size, rotation, bold, italic, underline, strikeout, font_name

obj_get_font_idA

Get or create a font ID for the given font properties.

Looks up (or creates) an entry in the Altium font table matching the specified properties and returns its font ID. Use the returned font_id to set an object's FontId property via obj_modify.

Args: size: Font size in points (e.g., 8, 10, 12) font_name: Font family name (default "Arial") bold: Whether the font is bold italic: Whether the font is italic rotation: Text rotation in degrees (0, 90, 180, 270) underline: Whether the font is underlined strikeout: Whether the font has strikeout

Returns: Dictionary with font_id

obj_selectA

Select objects matching a filter on the active document.

Sets the selection state on matching schematic or PCB objects for visual highlighting. Use obj_deselect_all to clear.

Args: object_type: Altium object type (schematic or PCB) filter: Pipe-separated property=value conditions (AND logic)

Returns: {"matched": N, "selected": M, "success": bool}. matched is what the filter picked; selected is how many of those read back as selected afterwards, and success is false with a reason when that is fewer. A select reports nothing about property readability.

obj_deselect_allA

Clear all object selection on the active document.

Returns: Dictionary confirming deselection

obj_zoomB

Control the viewport zoom level.

Args: action: "fit" (zoom to show all), "selection" (zoom to selected objects)

Returns: Dictionary confirming the zoom action

sch_mirror_componentA

Mirror a placed schematic component horizontally.

USE THIS RATHER THAN WRITING IsMirrored. Setting that property with obj_modify does not mirror anything: measured on AD26, the flag is set, it reads back, Altium's own Properties panel shows Mirrored ticked, and the part does not move. The canvas draws from the component's primitives, so a flag-only write leaves nothing new to draw and no redraw call can rescue it.

Altium's own mirror does both halves at once, setting the flag AND reflecting the primitives, and so does this. That matters twice over: a part mirrored here is identical on disk to one mirrored by hand, and the flag is instance data, so the result survives Update From Libraries. Reflecting the primitives alone would be undone the next time anyone re-instantiates the symbol.

Args: designator: component to mirror, e.g. "U1" mirrored: True or False for an absolute state; omit to toggle. Asking for the state it already has is a no-op, not a second reflection. doc_path: sheet to act on. Defaults to the focused schematic.

Every primitive is reflected about the component's X: pins (left and right swap), lines and rectangles by both ends, polygons, polylines and Beziers by every vertex, and arcs by their centre with their sweep mirrored. A primitive kind it cannot reflect is left where it was and counted in primitives_skipped.

Returns: Dictionary with designator, mirrored (the resulting state), primitives_moved, primitives_skipped, and changed.

obj_batch_modifyA

Apply many filter+set operations in ONE IPC round-trip.

PREFER THIS over looping obj_modify whenever you have more than one change to make, especially when each change targets a different object (different designator, different pin name, different sheet). A single obj_batch_modify call touches N objects in the same Altium transaction; N separate obj_modify calls cost N LLM round-trips plus N IPC round-trips. The wall-time difference is typically 10-100x on a multi-item edit.

Use it for: moving multiple pins to specific positions, re-laying component placement, per-sheet title changes, bulk designator rewrites, any workflow where each object gets its own value.

A DESIGNATOR BATCH IS WORTH A NETLIST CHECK AFTERWARDS. There is no transaction rollback here: if an op fails part way the earlier ones stay applied, and duplicate designators are not an error Altium reports. Two parts left sharing D? had their separate nets MERGED by the netlister, which is silent and survives File > Revert All (reported on a live project, issue #23). proj_annotate renumbers a whole project in one operation and cannot leave duplicates, so prefer it when that is what you want.

Args: operations: List of operation dicts, each with: - scope: "active_doc" (default), "project", "doc:C:\path\to\file.SchDoc", or "lib_component:NAME" -- a named symbol in the active SchLib. Mixing lib_component scopes across ops edits many library symbols in ONE call, no per-symbol lib_set_current_component round-trip. - object_type: Altium object type (e.g., "ePin", "eParameter", "eSchComponent", "eNetLabel"), or a PCB type such as "eTrackObject", which edits the active board and takes only scope "active_doc" - filter: Pipe-separated filter conditions (e.g., "Designator.Text=U1", "Name=VDD") - set: Pipe-separated property=value assignments (e.g., "Location.X=300|Location.Y=-100|Orientation=2")

Returns: Dictionary with operations_processed, operations_skipped, total_matched, and a per-operation results list. Each result row carries its own matched count plus a note -- an op that matched nothing reports note="no_objects_matched" instead of being silently counted as successful.

Example, edit pins across THREE different library symbols in ONE call: obj_batch_modify(operations=[ {"scope": "lib_component:R_0402", "object_type": "ePin", "filter": "Designator=1", "set": "Name=A"}, {"scope": "lib_component:C_0402", "object_type": "ePin", "filter": "Designator=1", "set": "Name=+"}, {"scope": "lib_component:LED_0603", "object_type": "ePin", "filter": "Designator=1", "set": "Name=K"}, ])

Example, reposition 10 pins on a library symbol in ONE call (vs. 10 separate obj_modify calls, each a full LLM turn): obj_batch_modify(operations=[ {"scope": "active_doc", "object_type": "ePin", "filter": "Name=S1", "set": "Location.X=200|Location.Y=-100|Orientation=2"}, {"scope": "active_doc", "object_type": "ePin", "filter": "Name=S2", "set": "Location.X=200|Location.Y=-200|Orientation=2"}, {"scope": "active_doc", "object_type": "ePin", "filter": "Name=VDD", "set": "Location.X=800|Location.Y=-100|Orientation=0"}, ... # 7 more pins ])

Example, update 4 parameters across every project sheet in ONE call: obj_batch_modify(operations=[ {"scope": "project", "object_type": "eParameter", "filter": "Name=Engineer", "set": "Text=John Smith"}, {"scope": "project", "object_type": "eParameter", "filter": "Name=Revision", "set": "Text=2.0"}, {"scope": "project", "object_type": "eParameter", "filter": "Name=Organization", "set": "Text=Acme Corp"}, {"scope": "project", "object_type": "eParameter", "filter": "Name=CompanyName", "set": "Text=Acme Corp"}, ])

Example, different titles on specific sheets in ONE call: obj_batch_modify(operations=[ {"scope": "doc:C:\path\TopLevel.SchDoc", "object_type": "eParameter", "filter": "Name=Title", "set": "Text=Top Level"}, {"scope": "doc:C:\path\PSU.SchDoc", "object_type": "eParameter", "filter": "Name=Title", "set": "Text=Power Supply"}, ])

obj_run_processB

Run an Altium process command via the generic primitive layer.

Wraps any Altium RunProcess call with structured pipe-separated parameters.

Args: process_name: The Altium process identifier (e.g., "Sch:Compile", "WorkspaceManager:OpenObject", "PCB:Zoom") parameters: Optional pipe-separated key=value parameter pairs, e.g.: "ObjectKind=Document|FileName=C:\path\to\file.SchDoc"

Returns: Dictionary with execution result

proj_run_ercA

Run Electrical Rules Check on the focused project.

Compiles the project first (required), then runs ERC. Call proj_get_erc_violations() afterwards to retrieve the structured list of violations.

Returns: Dictionary confirming ERC execution

sch_set_components_parametersA

Bulk-update parameters on PLACED schematic components.

Single IPC call wraps N component updates inside one PreProcess/PostProcess undo group. Avoids the 10-100x slowdown of looping the singular variant. The canonical follow-up tool after audits that flag parameter-level bugs -- audit_find_visible_supplier_pn, audit_find_placeholder_values, audit_find_mpn_inconsistencies -- recommend it.

Each stamp targets one component by designator and sets one or more parameters. Special keys allowed:

  • Value, Comment, Manufacturer, MPN, Footprint, Description, HelpURL, etc.

  • Custom parameters: any key name. Unknown parameters are CREATED if they don't already exist on the component.

Args: stamps: List of dicts. Each dict must include designator and one or more parameter key/value pairs. Example: [{"designator": "R1", "Value": "10k", "Manufacturer": "Yageo", "MPN": "RC0603FR-0710KL"}, {"designator": "C5", "Value": "100nF"}] sheet_path: Optional explicit SchDoc path. Empty uses the currently-focused schematic.

Returns: Dict with {updated, failed, total}. updated is the count of components whose parameters were touched; failed is the count of stamps that couldn't be applied (designator not found, parameter not settable, etc). A Footprint key repoints the part's footprint model; footprints_applied counts the ones that read back the new name and footprints_not_applied lists designators whose part has no footprint model or did not take it.

proj_get_erc_violationsA

Return the ERC violation list produced by the last proj_run_erc() invocation.

Two-step pattern by design: proj_run_erc() performs the compile-and-check (slow, can be 30+ s on a large project); this tool reads back the violations cheaply so the agent can re-query without re-running ERC.

Each violation names the OBJECTS it is about, not just a category. related_objects carries one entry per offending object with its kind (Pin, Net, Port), its document, and a cross_probe string, which is what Altium itself uses to jump to that exact object and is therefore what identifies the specific pin or net.

That detail is the difference between a report and something actionable. A category plus a sheet name cannot be acted on: the only safe response to "floating input pin, somewhere on this sheet" is to do nothing, because a NoERC marker placed by guesswork silently suppresses a real disconnection and is worse than the warning it clears.

related_object_count is reported separately, so a violation that genuinely exposes no objects is distinguishable from one whose objects could not be read.

Returns: {"violation_count", "violations": [{"index", "description", "detail", "related_object_count", "related_objects": [{"kind", "document", "cross_probe"}]}]}

obj_highlight_netA

Highlight a net by name in the focused schematic or PCB document.

PCB path sets IPCB_Net.IsHighlighted on the matched net (the documented property). Schematic path walks net labels / power ports / ports / sheet entries on the active sheet and marks those whose name matches as Selection=True, the closest thing Altium exposes to a "highlight" on schematic without interactive commands.

If a schematic is the focused document, this paints the schematic even when a PcbDoc is also open. Pass context="pcb" or context="schematic" to force one side. The previous behaviour always preferred the PCB whenever any board was loaded, which made a schematic Find-Net look like it did nothing.

Args: net_name: Exact net name to highlight (e.g., "VCC", "GND", "NET_D0"). Returns NOT_FOUND if the net doesn't exist on the PCB, or highlighted=0 on schematic if no primitive carries that net name on the active sheet (check other sheets). clear_existing: Clear existing highlights first (default True). context: Empty (default, follow the focused document), "schematic", or "pcb".

Returns: Dict with success, net, context ("pcb" or "schematic"), and highlighted (count of matches, 1 for PCB, N for sch).

obj_crossref_netA

Compare the schematic vs PCB membership of a named net.

Returns the full pin list the SCHEMATIC assigns to this net alongside the pad list the PCB assigns to the same net, plus the diff in each direction.

USE THIS when:

  • proj_get_nets / proj_get_connectivity returns an answer that surprises you or the user (e.g. a net appears to be missing a pin you expect on it, or appears to be disconnected from a component you know is wired).

  • Investigating "board works but schematic says otherwise": a non-empty pcb_only list means the PCB was fabricated from an older schematic revision, or an edit broke the post-ECO merge. Design > Update PCB from Schematic would rip up those PCB connections.

  • Debugging ECO workflow issues.

Args: net_name: Exact net name as it appears in the schematic (case-sensitive).

Returns: Dict with: - net_name - sch_pin_count, pcb_pin_count - matched (count of pins present on both sides) - sch_only_count, pcb_only_count - in_sync (True only when both sides list the same pins AND the net exists on at least one side) - sch_pins[], pcb_pins[], each entry is "Designator.PinNumber" - sch_only[], pcb_only[], the diff lists. If sch_only is non-empty the PCB is missing those connections. If pcb_only is non-empty the PCB has stale connections the current schematic doesn't have.

obj_clear_highlightsB

Clear all net highlights in the active schematic or PCB document.

Returns: Dictionary confirming highlights were cleared

proj_add_sheetA

Create a new schematic sheet and add it to the focused project.

The sheet is created in the same directory as the project file.

Args: name: Name for the new sheet (without .SchDoc extension). Default "NewSheet".

Returns: Dictionary with the path of the newly created sheet

proj_delete_sheetA

Remove a schematic sheet from the focused project.

Safety check: refuses to remove the last remaining schematic sheet. The file is closed and removed from the project but not deleted from disk.

Args: file_path: Full path to the .SchDoc file to remove

Returns: Dictionary confirming removal

obj_switch_viewA

Toggle between 2D and 3D view for PCB documents.

Args: mode: Target view mode, "3d" or "2d" (default "3d")

Returns: Dictionary confirming the view switch

obj_refresh_documentA

Force a redraw/refresh of the current document.

For schematics, calls GraphicallyInvalidate. For PCB, sends a PCB:Zoom Redraw command.

Returns: Dictionary confirming the refresh

proj_get_unconnected_pinsA

Find unconnected/floating pins in the focused project.

Compiles the project first (required for connectivity data), then iterates all components via the DM API to check pin connection status.

Returns: Dictionary with "count" and "unconnected_pins" array, each entry having designator, pin_number, pin_name, and sheet path

sch_stub_pinsA

Draw a short wire stub + net label on every unconnected schematic pin.

Finds floating pins (via the same connectivity check as proj_get_unconnected_pins), then for each one places a wire extending from the pin's electrical end outward along its orientation, terminated by a net label named "_<pin_number>". This makes dangling pins explicit so ERC reports them as named single-pin nets rather than silent floats, and gives a labelled anchor to wire up later.

Args: stub_length_mils: Length of each stub wire in mils (default 100) only_designators: If given, only stub pins on these components

Returns: Dictionary with "stubbed" and "failed" counts, plus the pin list that was acted on

sch_place_rectangleA

Place a rectangle shape on the active schematic (decorative only).

Args: x1, y1, x2, y2: Opposite corners in mils solid: Fill the rectangle (default False = outline only) line_width: 1 (small), 2 (medium), 3 (large)

Returns: Dictionary confirming rectangle placement

sch_place_lineA

Place a decorative line on the active schematic.

This is a graphic line, not a signal wire. Use sch_place_wires for electrical connections. Use this for hand-drawn borders, arrows, diagram overlays.

Args: x1, y1, x2, y2: Endpoints in mils line_width: 1 (small), 2 (medium), 3 (large)

Returns: Dictionary confirming line placement

sch_place_noteA

Place a text-box note on the active schematic.

Useful for design commentary, TODO markers, revision notes. The box sizes itself to (x1,y1)-(x2,y2) and contains the given text.

Args: x1, y1, x2, y2: Note box corners in mils text: Note content

Returns: Dictionary confirming note placement

sch_place_sheet_symbolA

Place a sheet symbol linking to a child schematic document.

Used for hierarchical designs, the parent sheet has one sheet symbol per child .SchDoc in the project. The sheet_file_name must exactly match a .SchDoc that's a project member. Without that match the sheet symbol is dangling.

After placing, use sch_place_sheet_entry to add sheet entries corresponding to the child sheet's ports.

Args: x1, y1, x2, y2: Sheet symbol box corners in mils sheet_file_name: Child SchDoc filename (e.g. "PSU.SchDoc") sheet_name: Display label (default: file name without extension)

Returns: Dictionary confirming sheet symbol placement

sch_place_sheet_entryA

Place a sheet entry port on an existing sheet symbol.

Sheet entries are the ports on a sheet symbol that map to hierarchical ports inside the child sheet. Name must match a port defined in the child .SchDoc for electrical continuity.

Args: sheet_name: SheetName of the target sheet symbol entry_name: Port name (must match a port in the child sheet) io_type: "input" | "output" | "bidirectional" | "unspecified" side: "left" | "right" | "top" | "bottom" distance_from_top: Position along the chosen side in mils

Returns: Dictionary confirming sheet entry placement

sch_place_bus_entryA

Place a bus entry (45 degree stub) connecting a wire to a bus.

Bus entries are the angled stubs that tap a single-signal wire off a bus line. Start point should sit on the bus; end point should sit on the wire (both usually at 45 degrees offset).

Args: x1, y1: Start coordinates in mils (typically on the bus) x2, y2: End coordinates in mils (typically on the wire)

Returns: Dictionary confirming bus entry placement

sch_set_sheet_sizeA

Set the sheet size / template style of the active schematic.

Changes SheetStyle on the current SchDoc. Use a named style (A-E, A0-A4, Letter, Legal, Tabloid) or "Custom" with explicit width and height in mils. Give style, orientation or both.

Args: style: Sheet size name, e.g. "A", "A3", "A4", "Letter", "Custom" custom_width: Custom sheet width in mils (only if style="Custom") custom_height: Custom sheet height in mils (only if style="Custom") orientation: "landscape" or "portrait"; empty leaves it as is

Returns: Dictionary confirming the style change

sch_place_busA

Place a bus segment between two XY coordinates on the active schematic.

Buses carry multi-signal groups like DATA[0..7]. Place a bus line and then attach net labels with the bus naming syntax to mark the signal group. Typical workflow: bus line, bus entries at each pin, then connect each bus entry to a wire.

Args: x1: Start X coordinate in mils y1: Start Y coordinate in mils x2: End X coordinate in mils y2: End Y coordinate in mils

Returns: Dictionary confirming bus placement with coordinates

sch_place_net_labelB

Place a net label at coordinates on the active schematic.

Args: text: Net name for the label (e.g., "VCC", "GND", "SDA") x: X coordinate in mils y: Y coordinate in mils orientation: Label rotation (0=0deg, 1=90deg, 2=180deg, 3=270deg)

Returns: Dictionary confirming placement

sch_place_portA

Place a port on the active schematic for inter-sheet connectivity.

Args: name: Port name (maps to net name) x: X coordinate in mils y: Y coordinate in mils style: Arrow style, "none", "left", "right", "left_right" io_type: I/O direction, "unspecified", "output", "input", "bidirectional"

Returns: Dictionary confirming placement

sch_place_power_portA

Place a power port symbol (VCC, GND, etc.) on the active schematic.

Args: text: Net name for the power port (e.g., "VCC", "GND", "+3V3") x: X coordinate in mils y: Y coordinate in mils style: Symbol style: "circle", circle symbol (default, typical for VCC) "arrow", arrow symbol "bar", bar/line symbol "wave", wave symbol "gnd_power", power ground symbol "gnd_signal", signal ground symbol "gnd_earth", earth ground symbol orientation: which way the glyph points from the connection point: 0=right, 1=up, 2=left, 3=down. Leave at -1 to let the bridge decide by style, which sends the ground glyphs down and everything else up.

    PASS IT EXPLICITLY FOR A RAIL DRAWN WITH style="bar".
    The style-based default groups "bar" and "wave" with the
    grounds, so a VCC bar comes out pointing DOWN and reads
    as a ground symbol. orientation=1 is what you want there.

Returns: Dictionary confirming placement

sch_get_sheet_parametersA

Get title block parameters (title, revision, date, etc.) from a schematic sheet.

Reads all parameters from the sheet without modifying anything.

Args: file_path: Full path to a specific .SchDoc file. If empty, reads from the active document.

Returns: Dictionary with "count" and "parameters" array, each having "name" and "value"

obj_copyA

Copy matching schematic objects to the clipboard.

Selects objects matching the filter, copies them to the system clipboard, then clears selection.

Args: object_type: Altium schematic object type (see obj_query) filter: Pipe-separated property=value conditions (AND logic)

Returns: Dictionary with count of copied objects

obj_countA

Quick count of objects by type, faster than obj_query when you only need the count.

Args: object_type: Altium object type constant (see obj_query for options) scope: "active_doc" (default), "project", or "doc:C:\path\Sheet.SchDoc" for a specific loaded sheet. filter: Pipe-separated property=value conditions (AND logic)

Returns: Dictionary with "count" (and "sheets_processed" for project scope)

sch_place_no_ercA

Place a No-ERC marker at coordinates to suppress specific ERC violations.

Use this after running ERC to suppress known-good violations at specific pin or wire locations.

Args: x: X coordinate in mils y: Y coordinate in mils

Returns: Dictionary confirming placement with coordinates

sch_place_junctionA

Place a MANUAL junction at coordinates on the active schematic.

A T NEEDS NONE. Altium draws an automatic junction wherever a wire ends on another wire or three wire ends meet, blue, exactly as interactive wiring leaves it, and it does so for wires this server places too. The object this tool creates is a manual junction: drawn dark red with a lock marker, and placed on a T it just sits on top of the blue one.

Use it only where two wires CROSS and must connect. A plain crossing is not a connection in Altium, and a manual junction is what makes it one.

Args: x: X coordinate in mils y: Y coordinate in mils

Returns: Dictionary confirming placement with coordinates

obj_get_document_infoA

Get comprehensive info about the active document.

For schematics: file path, kind, sheet size, title block visibility, snap grid, visible grid, unit system, and custom dimensions. For PCB: file path, kind, origin, snap grid.

Returns: Dictionary with document properties

obj_set_gridA

Set the snap grid and/or visible grid size for the active schematic.

Args: snap_grid: Snap grid size in mils (0 = don't change) visible_grid: Visible grid size in mils (0 = don't change)

Returns: Dictionary with the resulting grid sizes

sch_set_unitsA

Set the unit system for the active schematic.

Calls ISch_Document.SetState_Unit with a TUnit enum value. The current unit is readable via obj_get_document_info (unit_system field).

Args: unit: one of "mil" , imperial, mils (0.001 in) "inch" , imperial, inches "dxp" , DXP default "auto_imperial" , auto-scaled imperial display "mm" , metric, millimetres "cm" , metric, centimetres "m" , metric, metres "auto_metric" , auto-scaled metric display

Returns: Dictionary confirming the change, with the resulting unit_system ("imperial" or "metric").

sch_add_directiveA

Place a parameter-set directive at (x, y) on the active schematic.

A parameter-set directive attaches a single name=value design parameter to a wire or net. Drop the directive on top of the target wire/net and the compile engine will propagate the parameter. Common uses:

  • param_name="DifferentialPair", param_value="USB" , marks the net as a member of the USB differential pair.

  • param_name="NetClass", param_value="HighSpeed" , assigns the net to the HighSpeed class.

  • param_name="Signal_Stimulus", param_value="..." , any custom per-net rule parameter.

Args: x, y: Location in mils, place ON the target wire/net. param_name: Parameter name (e.g. "NetClass", "DifferentialPair") param_value: Parameter value

Returns: Dictionary confirming placement.

sch_get_directivesA

Enumerate parameter-set directives on the active schematic sheet.

Returns every ISch_ParameterSet on the sheet along with its location and the list of parameters it carries. Use this to audit which nets have explicit NetClass / DifferentialPair / custom-rule assignments before a compile, or to verify a bulk placement worked.

Returns: Dictionary with directives array, each entry has name, x, y, and a parameters list of {name, value} objects, plus count.

sch_place_imageA

Place an image/logo on the active schematic.

Args: image_path: Full path to the image file (BMP, JPG, PNG, etc.) x: X coordinate in mils (bottom-left corner) y: Y coordinate in mils (bottom-left corner) width: Image width in mils (default 500) height: Image height in mils (default 500)

Returns: Dictionary confirming placement with path and dimensions

proj_replace_componentA

Replace a component with a different library part.

Keeps existing connections and designator, swaps the symbol and library reference. The component must exist on the active schematic sheet.

Args: designator: Component designator to replace (e.g., "U1", "R3") new_lib_ref: New library reference / component name new_library: New source library name (optional, keeps current if empty)

Returns: Dictionary confirming the replacement

sch_clear_source_libraryA

Unpin placed schematic components from a stale source library.

Schematic mirror of pcb_clear_source_footprint_library. When a library is renamed, moved, or consolidated, placed components keep pointing at it through two component PROPERTIES:

  • SourceLibraryName: which library file the part came from.

  • DesignItemId: which library ITEM it re-matches against ("Design Item ID" in the Properties panel). A re-link that updates only LibReference leaves this naming the OLD item, which is exactly the <Not Found> state in the panel.

Per matching component this clears SourceLibraryName (when set) and syncs DesignItemId to LibReference (when they differ), so Altium re-matches from whatever is currently in Available Libraries. Both actions are independently switchable.

DesignItemId is a component property, NOT a parameter: writing it through the parameter-stamping API only creates a stray user parameter of that name. Use this tool (bulk) or obj_modify with property DesignItemId (single component).

Args: sheet_path: absolute .SchDoc path; empty = the focused sheet. For a whole project, call once per sheet (see proj_list_documents). designators: restrict to these designators (whole-string, case-sensitive); None/empty = every component on the sheet. clear_source_library: clear SourceLibraryName. sync_design_item_id: set DesignItemId = LibReference where they differ.

Returns: {"total": inspected, "cleared_source_library": n, "synced_design_item_id": n}.

sch_get_constraint_groupsA

Enumerate IDocument.DM_ConstraintGroups on the active schematic.

Constraint groups are FPGA-style pin/timing constraints attached to a document. Each group has a target kind and id, plus a list of IConstraint entries with a kind/data payload. Useful for auditing FPGA pin assignments and timing constraints that don't show up in the regular PCB design-rule list.

Returns: Dict with groups (list of {target_kind, target_id, constraint_count, constraints[{kind, data}, ...]}) and count.

sch_place_harness_connectorA

Place a harness connector on the active schematic sheet.

Harness connectors group a set of wires/buses into a named signal bundle, letting cross-sheet connections be drawn as a single line instead of a bus.

Args: x, y: Bottom-left corner in mils. width, height: Connector rectangle size in mils. harness_type: Named harness type (optional).

Returns: Dict with success, x, y, width, height, harness_type.

sch_place_cross_sheet_connectorA

Place a cross-sheet connector (off-sheet port) on the active sheet.

Cross-sheet connectors are the hierarchical equivalent of a net label, they connect a signal to the same net name on another sheet.

Args: x, y: Connector location in mils. net: Net name the connector binds to. side: "left" or "right" orientation (optional).

Returns: Dict with success, x, y, net.

sch_place_text_frameA

Place a multi-line text frame (note block) on the active sheet.

A text frame is a bordered rectangle that word-wraps a block of text. Use \n in text for explicit line breaks.

Args: x1, y1, x2, y2: The two rectangle corners in mils. text: Frame contents (use \n for line breaks). align: Horizontal alignment "left", "center", or "right".

Returns: Dict confirming placement and the frame corners.

sch_generate_tocA

Place a table-of-contents note listing every schematic sheet.

Reads the project's document list, keeps the .SchDoc sheets in project order, and drops a numbered text frame on the active sheet. Run it on a dedicated cover/index sheet. Re-running does not remove a prior frame, delete the old one first if you regenerate.

Args: x1, y1, x2, y2: Frame corners in mils (default a tall left box). title: Heading line above the list. project_path: Optional project; uses active if omitted.

Returns: Dict with sheet_count and the placement result, or an error if no schematic sheets were found.

sch_set_net_tieA

Mark a placed schematic component as a net tie.

A net tie shorts the nets that land on its pins for routing while keeping them as separate logical nets (so ERC/DRC treat the join as intentional). Sets the component's ComponentKind. Use on a dedicated net-tie footprint/symbol you have already placed and wired.

Args: designator: Component to convert (e.g. "NT1"). mode: "nobom" (default, hidden from BOM, kept through sync) or "bom" (appears in the BOM).

Returns: {"designator", "component_kind"} or NOT_FOUND.

sch_increment_designatorsA

Offset the trailing number of schematic designators by a delta.

Adds delta to the numeric suffix of every component designator on the active sheet (e.g. delta=100 turns R5 into R105), optionally restricted to a designator prefix. Handy after copying a block to avoid designator collisions before re-annotating.

Args: delta: Non-zero integer added to each designator's number. prefix: If set (e.g. "R"), only designators with this letter prefix are changed.

Returns: Dict with success, modified (count), delta.

sch_toggle_pin_visibilityA

Show or hide pin name / designator labels on schematic symbols.

Targets a single component (by designator) or every component on the active sheet when designator is empty. Provide show_name and/or show_designator to set those flags; omit a flag to leave it unchanged.

Args: designator: Component to target, or "" for all components. show_name: True/False to set pin-name visibility (optional). show_designator: True/False to set pin-designator visibility.

Returns: Dict with success and pins_modified (count).

sch_set_component_part_idA

Switch the active sub-part on a multi-part schematic component.

Multi-gate parts like quad op-amps expose one sub-part per gate (U1A, U1B, U1C, U1D). CurrentPartID selects which one this symbol instance represents. IDs are 1-based.

A DESIGNATOR IS NOT UNIQUE HERE. Every sub-part of a multi-part device carries the same one, so "U13" can name three symbols on the sheet. When it names more than one and no location is given this REFUSES, and returns the candidates with their locations, current part ids and unique ids. It used to write whichever the iterator reached first and report success naming only the designator, so the caller could not tell which symbol changed.

Location is the discriminator because the alternatives do not work: two symbols can sit on the same part, and sub-parts of one physical device are supposed to SHARE a unique id.

Args: designator: Component reference (e.g., "U1"). part_id: Sub-part index, 1-based (1=A, 2=B, ...). A value above PartCount is accepted by Altium and does not take, so the result is read back and a mismatch is reported. location_x: Symbol X in mils, to choose one of several. location_y: Symbol Y in mils, to choose one of several.

Returns: Dict with success, designator, part_id, the location written and how many symbols carried the designator. On ambiguity, an AMBIGUOUS_DESIGNATOR error carrying candidates.

sch_set_component_unique_idA

Set UniqueId on every placed symbol with this designator.

ECO treats sub-parts of a multi-gate symbol (quad comparator, dual op-amp) as one physical footprint only when they share UniqueId. Four copies of part 1 with four UniqueIds become four packages.

EVERY MATCHING SYMBOL IS STAMPED, not the first. Sharing the id is the whole point of the property, so writing one of three and reporting success left the other two pointing at packages of their own, which is the exact condition this repairs.

Altium mints UniqueIds itself and may keep its own. Each write is read back and COMPARED; if any symbol kept its old id the call fails and names which ones, because a partial stamp still leaves the device split.

Args: designator: Component reference as drawn (e.g. "U_COMP2B"). unique_id: Target UniqueId, usually copied from the A unit.

Returns: Dict with success, designator, unique_id, symbols_written, symbols_found, and per-symbol locations with the id each reads back.

sch_replicate_componentA

Copy a placed schematic part and share the master's UniqueId.

ECO groups multi-part packages (quad comparators, dual op-amps) only when every unit has the same UniqueId. Writing UniqueId after the copy is already on the sheet remints a new id if that value is already present. This path stamps UniqueId on the replica before AddSchObject, which is the share that sticks.

Finds the first instance whose designator matches, Replicates it, optionally switches CurrentPartID, optionally moves it, and keeps the master's UniqueId.

Args: designator: Source instance to copy (e.g. "U8"). part_id: 1-based CurrentPartID for the copy. 0 = keep source. x, y: Placement in mils. Omit both to leave the replica where Replicate dropped it. new_designator: Designator for the copy. Empty keeps the source designator (required for multi-part packing).

Returns: Dict with success, source/copy UniqueIds, part_id, and shared (true when the copy kept the master's UniqueId).

sch_place_probeA

Place a probe/measurement marker on the active schematic.

Probes mark nodes for SPICE/simulation output. Attach one to a wire to declare "capture voltage / current at this point" in simulation runs.

Args: x, y: Probe location in mils. net_name: Optional explicit net label text. probe_method: "probed_nets_only" (default) or "all_nets".

Returns: Dict with success, x, y, net_name.

sch_add_datafile_linkA

Attach a datafile link to a component's current implementation.

Datafile links are how parametric data (IBIS model files, sim models, external CSVs) is bound to a schematic part.

Args: designator: Component reference (e.g., "U1"). file_path: Full path to the file being linked. kind: Optional implementation-specific type (e.g., "SimModel", "IBIS").

Returns: Dict with success, designator, file_path.

obj_batch_createA

Create many schematic objects in ONE IPC round-trip.

PREFER THIS over looping obj_create. Each create costs one LLM turn when done one at a time; batched it's a single PreProcess/PostProcess + one save for the whole set.

Args: operations: List of create dicts, each with: - scope: "active_doc" (default), only active_doc is currently honored for creates. - object_type: Altium type name (e.g. "eNetLabel", "eJunction", "eNoERC"). - properties: pipe-separated Name=Value list, same format as obj_create accepts. - container: "document" (default) or "component" (for library-symbol contents when a lib is active).

Example, drop 3 net labels in one call: obj_batch_create(operations=[ {"object_type": "eNetLabel", "properties": "Text=VCC|Location.X=100|Location.Y=200"}, {"object_type": "eNetLabel", "properties": "Text=GND|Location.X=100|Location.Y=400"}, {"object_type": "eNetLabel", "properties": "Text=SCK|Location.X=300|Location.Y=200"}, ])

Returns: Dict with created, failed, total counts and indexed failures. Known unsupported properties (IsHidden on anything but pins, parameters, designators and sheet names; Text on a type without it, such as eParameterSet or eWire) reject that item before registration. Its failure includes reason UNSUPPORTED_PROPERTY, property and object_type. Other valid items still run.

obj_batch_deleteA

Delete matching objects across many scope/type/filter operations.

PREFER THIS over looping obj_delete. Each op is evaluated in one go, cleaning a mixed set of stale junctions, no-ERCs, and net labels costs one IPC round-trip instead of N.

Args: operations: List of delete dicts, each with: - scope: "active_doc" (default), "project", or "doc:<absolute_path>". - object_type: Altium type name (e.g. "eJunction", "eNoERC", "eWire", or a PCB type such as "eTrackObject", which deletes from the active board and takes only scope "active_doc"). - filter: pipe-separated PropName=Value filter conditions (AND logic), same format as obj_delete. An EMPTY filter deletes every object of that type in that scope. confirm_delete_all: required when any operation has an empty filter. Same guard obj_delete applies to the single call, so routing a sweep through the bulk tool does not get around it.

Example, purge all no-ERCs on a specific sheet and every junction on the project. Both filters are empty, so the confirmation is required: obj_batch_delete(confirm_delete_all=True, operations=[ {"scope": "doc:C:\proj\Power.SchDoc", "object_type": "eNoERC", "filter": ""}, {"scope": "project", "object_type": "eJunction", "filter": ""}, ])

Returns: Dict with operations_processed, total, total_deleted and results: one row per operation with its deleted count and a note saying why it deleted nothing (unknown_object_type, scope_not_supported, no_objects_matched, or failed: CODE). Or an error with operations_processed 0 when a sweep is unconfirmed, in which case nothing is sent.

sch_place_wiresA

Place MANY wire segments on the active schematic in ONE call.

NOT THE WAY TO WIRE A DESIGN. Routing a netlist segment by segment means choosing every corner yourself, and the result reads like nothing a person would draw. design_execute_plan routes the whole sheet from a DesignPlan through the layout engine, deciding wire-versus-label per net and keeping wires off other nets' pins. Use THIS tool for a few segments on a sheet that already exists.

PREFER THIS over placing wires one segment at a time (there is no singular variant). Wiring up a netlist is inherently N pairs of endpoints; the bulk version is 10-100x faster in wall time because the whole batch shares one PreProcess/PostProcess and one redraw.

Args: wires: List of wire dicts, each with x1, y1, x2, y2 in mils.

Example, a 3-segment L-shaped bus routing: place_wires(wires=[ {"x1": 100, "y1": 200, "x2": 300, "y2": 200}, {"x1": 300, "y1": 200, "x2": 300, "y2": 400}, {"x1": 300, "y1": 400, "x2": 600, "y2": 400}, ])

Returns: Dict with placed, failed, total counts.

sch_place_componentsA

Place MANY schematic components from libraries in ONE call.

NOT THE WAY TO DRAW A SCHEMATIC, AND IT IS ENFORCED. Placing four or more components onto a sheet that has two or fewer is refused: that is a schematic being drawn by hand, and positions chosen that way read like nothing a person would draw however correct the netlist is. Build a DesignPlan and call design_execute_plan instead, which places AND routes the whole sheet through a layout engine measured against hand-drawn boards; design_layout_schematic and design_preview_plan show you the result first without touching the EDA. Use THIS tool to add parts to a sheet that already exists, or when the positions came from that engine. To place coordinates yourself anyway, pass allow_manual_layout=True.

This is the only path for library placement (there is no singular variant). Laying out a 50-part BOM is inherently a bulk operation; done one-by-one it would cost 50 LLM turns.

TARGET DOCUMENT: placement lands on the ACTIVE schematic. Right after app_create_document the new sheet is NOT auto-focused, so without document_path parts can silently land on a different open sheet. Pass document_path (absolute .SchDoc path) and this tool focuses that sheet first (app_set_active_document) before placing, always set it when you have just created the target sheet.

Args: allow_manual_layout: Set True to bypass the refusal described above and place your own coordinates onto an empty sheet. The default exists because this is almost never what the caller actually wants; say why in the surrounding turn. document_path: Absolute path of the .SchDoc to place onto. When given, it is focused before placement so parts can't land on the wrong sheet. When omitted, the current active document is used. placements: List of placement dicts, each with: - library_path (str, optional, empty uses an already-open library) - lib_reference (str, required), component name - x, y (int, mils), placement location - designator (str, optional), override designator - rotation (int, optional), 0 / 90 / 180 / 270 - footprint (str, optional), override current footprint

Example, place a 5-part BOM row: sch_place_components(placements=[ {"library_path": "C:\Lib\ST.SchLib", "lib_reference": "STM32F411RE", "x": 1000, "y": 2000, "designator": "U1"}, {"library_path": "C:\Lib\Res.SchLib", "lib_reference": "Res1", "x": 1500, "y": 2000, "designator": "R1"}, ... # 3 more ])

Returns: Dict with placed, failed, total counts.

sch_place_vault_componentsA

Place MANY managed (Workspace) components in ONE call.

sch_place_components for parts that live in an Altium Workspace rather than a library file. Such a part has no library path; it is placed by its Design Item ID and stays linked to its Workspace item. The hand-drawing rule of sch_place_components applies here too: four or more parts onto a sheet with two or fewer is refused unless allow_manual_layout=True.

A part number is not looked up here, so bring the Design Item ID. lib_get_vault_component checks one without placing it.

Args: placements: List of dicts, each with "design_item_id" (required), x and y in mils, and optionally designator and rotation (0, 90, 180 or 270). vault: The Workspace name, for the whole batch. Usually left out: it is read off managed parts already on an open sheet of the design, where Altium wrote it. When no open sheet has one, the call is refused and says what to do. Ask the user for the name rather than making one up. document_path: Absolute path of the .SchDoc to place onto. It is focused first; without it the active sheet is used, and a sheet just created is not active yet. lib_identifier_kind: The identifier kind to load by, 4 (VaultName) when left out. lib_get_vault_component reports the kind that loaded. allow_manual_layout: Place onto a near-empty sheet anyway.

Returns: placed, linked, failed, total, vault, lib_identifier_kind, loader_callable, note and one items entry per placement. linked counts the parts that read back a VaultGUID and ItemGUID, the link the file saves; a part placed but not linked is a plain copy of the symbol (reason PLACED_WITHOUT_LINK). NOT_FOUND_IN_VAULT means that ID did not load; LOAD_RAISED means this Altium cannot load managed parts at all.

sim_attach_primitivesA

Attach SPICE primitives to MANY components in ONE call.

PREFER THIS after running sim_get_readiness, the readiness response typically lists 20-50 passives that all need the SpicePrefix + Value parameter pair. Attaching them one at a time would cost one LLM turn per component (there is no singular variant); this tool does the whole set in one round-trip.

Args: attachments: List of attach dicts, each with: - designator (str, required) - primitive (str, required), R/L/C/V/I/D/Q/M/X - value (str, optional), "10k", "100n", "DC 5", "SIN(0 1 1k)", etc. - spice_model (str, optional), model name for semi/sub-circuit parts - sim_kind (str, optional), "General" / "Subcircuit" / "Model"

Example, attach the 4 passives from a readiness audit: sch_attach_spice_primitives(attachments=[ {"designator": "R1", "primitive": "R", "value": "10k"}, {"designator": "R2", "primitive": "R", "value": "10k"}, {"designator": "C1", "primitive": "C", "value": "100n"}, {"designator": "C2", "primitive": "C", "value": "1u"}, ])

Returns: Dict with attached, failed, total counts.

pcb_get_netsB

Get all unique net names from the active PCB board.

Returns: Dictionary with "nets" array of net name strings and "count"

pcb_focus_boardA

Make a specific PCB the focused/current board.

When several PcbDocs are open, the other PCB tools (pcb_get_components, pcb_delete_object, pcb_delete_net, pcb_plan_placement, design_visual_review, …) operate on the focused board, and app_set_active_document does NOT reliably set that for a PcbDoc. Call this first to point them all at the board you mean. (pcb_place_components already accepts board_path directly.)

Args: board_path: Absolute path to the .PcbDoc to focus.

Returns: Dict with focused and the resolved board file name.

pcb_delete_netA

Delete nets from the active PCB.

By default removes only EMPTY nets (no connected pads / tracks / vias): the cleanup for stray nets left behind after deleting components, e.g. nets created by pcb_place_components' synced mode. A net that still has connections is skipped unless force=True (forcing orphans those pads/tracks, so use it deliberately).

Args: nets: Specific net names to delete. Omit (or pass an empty list) to sweep ALL empty nets on the board. force: Also delete nets that still have connected primitives (orphans them). Default False.

A net name containing a COMMA is refused rather than sent. The list rides one comma-separated field and the handler splits it with Pos(',', ...), so such a name would arrive as two, and the fragments can match real nets: VCC,GND becomes VCC and GND. With force=True that deletes nets nobody named and orphans their pads and tracks.

Returns: Dict with deleted (count removed), skipped_connected (count), and skipped_nets (names skipped because still connected). Or {"ok": False, "reason": ...} if a name contains a comma, in which case nothing is sent.

pcb_get_net_classesA

Get all net classes on the active PCB, with the nets in each.

Altium's script host does not expose a class's member list, so each class is asked about every net in turn. That is one call per class and net, so on a big board members=False is quicker when only the names are needed.

Args: members: Read each class's nets (default). False returns the class names only.

Returns: Dictionary with "net_classes" (each with name, super_class and, when members are read, "nets", "member_count" and "probe_errors"), "count" and "members_read". A class with probe_errors above 0 may have more nets than listed.

pcb_create_net_classA

Create a net class (or add nets to an existing one) on the active PCB.

Args: name: Name for the net class (e.g., "PowerNets", "HighSpeed") nets: Comma-separated list of net names to add (e.g., "VCC,GND,3V3")

Returns: Dictionary with class_name, class_created (bool), nets_added count

pcb_create_nets_from_listA

Create net objects on the active PCB for names not already there.

First leg of the netlist-driven SCH->PCB bridge (Altium's ECO is not scriptable): place footprints with pcb_place_components, create the nets here, bind pads with pcb_bind_pad_nets, then verify with proj_compare_sch_pcb. The whole list is one IPC round-trip; names already on the board are counted, not duplicated.

Args: nets: Net names to ensure exist (e.g. from the compiled netlist). Duplicates and empty strings are ignored.

Returns: Dict with created and existing counts.

pcb_bind_pad_netsA

Assign component pads to existing board nets, batch in ONE call.

Second leg of the netlist-driven SCH->PCB bridge: after pcb_create_nets_from_list, feed every (designator, pin, net) row of the compiled netlist here. Each binding finds the placed component, matches the pad by name, and sets its net. Nets must already exist on the board -- a binding to a missing net fails (it does not create the net).

Bindings are processed in order; keep rows for the same designator adjacent (the handler caches the component lookup per consecutive run -- pcb_build_from_project orders them this way automatically).

Args: bindings: List of dicts, each with designator (e.g. "U1"), pin (pad name, e.g. "3" or "A1"), net.

Returns: Dict with bound / failed counts plus missing_components / missing_pads / missing_nets name lists (each capped at 50 entries).

pcb_build_from_projectA

Build the PCB's net set and pad connectivity from the netlist.

The SCH->PCB bridge orchestrator (replaces the unscriptable ECO netlist transfer): derives the distinct net list and every (designator, pin, net) binding from the compiled netlist, creates the missing nets (pcb_create_nets_from_list) and binds every pad (pcb_bind_pad_nets) in two IPC round-trips.

It does NOT place footprints -- pcb_place_components exists for that. Intended sequence: place components, then run this to build nets and bind pads, then verify with proj_compare_sch_pcb.

Args: netlist_csv_path: Optional path to a tabular netlist CSV written by proj_export_netlist(net_format="tabular") (component,pin,pin_name,net rows). Empty (default) = pull the compiled netlist from the open project directly.

Returns: Dict with components (distinct designators in the netlist), nets_created, nets_existing, pads_bound, failed, plus the missing_components / missing_pads / missing_nets lists from the bind step.

pcb_get_design_rulesA

Get all design rules from the active PCB.

Reports every rule kind, including ones this toolset cannot create, because it iterates rule objects rather than switching on a kind.

rule_kind IS AN ALTIUM ENUM ORDINAL, NOT A NAME. It is the raw integer, so it is only meaningful against the build that produced it and is not worth comparing across versions. Read descriptor instead: Altium composes it for display, so it names the kind and carries the constraint values.

A CLEARANCE-FAMILY RULE ALSO CARRIES gap_mm, read from the rule itself. Trust it over the descriptor for Board Outline Clearance, whose descriptor says Gap=0mil whatever the rule holds (measured on AD 26.10: 0.254 mm and 3 mm both described as 0). That rule's per-object table is not exposed to scripts, so a value set only in the table is visible in Design > Rules alone.

Returns: Dictionary with "rules" array (each with name, rule_kind, enabled, priority, scope_1, scope_2, comment, descriptor, and gap_mm for clearance, component, hole-to-hole and board outline clearance rules) and "count".

pcb_set_rules_enabledA

Bulk-toggle the DRC-enabled flag on design rules by name.

Useful for focused review passes: disable a noisy class of rules to surface the violations that matter (e.g. silence Silk-to-Silk while you sweep Clearance), or re-enable a set before a release sweep. Matches case-insensitively; supports trailing-* wildcards so a rule family can be targeted without listing every name ("DiffPair_*" hits every rule whose name starts with that).

Note that this flips DRCEnabled (whether DRC actually checks the rule), not Enabled (whether the rule exists in the rule list); the former is the right knob for review iteration.

Args: names: Rule names to toggle. With match="name" (default) each entry is matched case-insensitively against Rule.Name; a trailing * wildcards the suffix ("Clearance*"). With match="kind" each entry is a TRuleKind ordinal as a string (look up via pcb_get_design_rules). enabled: New value for DRCEnabled. True turns the rule ON in DRC; False disables it for this run. match: "name" (default) or "kind".

Returns: Dict with: - matched: rules that matched any pattern - updated: how many actually changed value - enabled: the requested target value (for confirmation) - items: per-matched {name, kind, prev_enabled, new_enabled}

pcb_get_clearance_violationsA

Read the violations ALREADY on the board, optionally filtered to one net. Does NOT run DRC.

This is a pure board read: it walks the eViolationObject records the last DRC run (batch or online) left behind. It opens no dialog and cannot block the bridge. Use it as the default way to inspect violations.

It used to trigger PCB:DesignRuleCheck first, which raises Altium's MODAL "Design Rule Checker" setup dialog: on a 241-component board that wedged the whole bridge for 30+ min until the client timed out. A "get" tool must not do that, so the trigger is gone. pcb_run_drc(allow_modal=True) is now the only way to force a fresh run.

CAVEAT -- violation_count: 0 means "no violations are stored on this board", NOT "the board passes". If DRC has never been run in this Altium session, there is nothing to read and the answer is 0 either way. The response carries drc_triggered: false and a note saying so. To get fresh data, run DRC from Altium's UI (Tools > Design Rule Check) or call pcb_run_drc(allow_modal=True) and be ready to click the dialog.

Filter is substring-matched against the violation's Description and Name, so it catches both "Net USB_DP and Net GND" clearance warnings and net-named via antennas.

Each violation carries x_mils / y_mils / layer for the agent to navigate to, plus primitive1 / primitive2 objects with {detail, type, net, layer, x_mils, y_mils}.

Args: net: Net name to filter by (substring match). Empty string returns ALL stored violations. offset: How many matching violations to skip, for paging. limit: How many to return, 200 by default.

Returns: Dict with {violation_count, offset, limit, returned, has_more, drc_triggered, note, violations}. violation_count is every violation that matched; when has_more is true, call again with offset raised by returned.

pcb_run_drcA

Run Design Rule Check (DRC) on the active PCB. MODAL -- BLOCKS THE BRIDGE. Opt in with allow_modal=True.

DANGER: Altium has no non-interactive DRC trigger exposed to DelphiScript. PCB:DesignRuleCheck raises the MODAL "Design Rule Checker [mil]" setup dialog and the worker sits behind it until a HUMAN clicks Cancel/OK. Nothing else on the bridge runs meanwhile: one observed call left the loop dead for 30+ min, the client timed out at 1800 s, and every in-flight job was stranded. No parameter suppresses the dialog, so this tool refuses to fire unless the caller passes allow_modal=True, which is a promise that a human is at the keyboard.

PREFER pcb_get_clearance_violations -- it reads the violations already stored on the board, runs no DRC, and cannot block.

PATHOLOGICAL CASE: a full DRC on a placed-but-unrouted board (many components, zero tracks, everything unrouted) checks every pad pair against every rule and is enormously slow for an answer that is already known -- "nothing is routed yet". Check pcb_get_board_statistics / pcb_get_unrouted_nets first; if track count is 0, do not run DRC.

With allow_modal=True it executes the DRC and returns up to 100 violations with full location data, so the agent can jump straight to the offending spot instead of guessing from the description.

drc_confirmed is true only when Altium rewrote the .DRC report during this call. A cancelled dialog leaves the old report untouched, so its count, 0 included, belongs to an earlier run. It also stays false when the checker's Create Report File option is off.

Per-violation shape:

  • name, description, rule: text from Altium

  • x_mils / y_mils / layer: violation's bbox centre (the "go here" hint)

  • primitive1 / primitive2: objects with {detail, type, net, layer, x_mils, y_mils}. type is the object kind (Track / Pad / Via / Region / Comp); net is the net name. Two primitives are involved in most rule kinds (Clearance, Short Circuit); a single- primitive violation (e.g. AcuteAngle) leaves primitive2 with empty fields.

Use primitive1.net and primitive2.net to spot the nets in conflict; use x_mils / y_mils to drive proj_cross_probe or the dashboard's Drawing tab to the site.

Args: allow_modal: Must be True to actually run DRC, and it means "I accept that the bridge blocks on a modal dialog until a human clicks it". Default False refuses without touching Altium.

Returns: Dict with violation_count (full count even if > 100), drc_triggered, and violations array (first 100). With allow_modal=False: {ok: False, reason: ...} and no bridge call at all.

pcb_get_componentsA

Get all components from the active PCB with position and properties.

DATASHEET DISCIPLINE: The response carries a _datasheet_guidance block. Before drawing any conclusion about a listed part's electrical behavior, pin function, or voltage rating, fetch its manufacturer datasheet (use WebSearch + WebFetch if you don't already have it). Library metadata here is NOT authoritative.

Returns: Dictionary with "components" array (each with designator, x, y, rotation, layer, footprint) and "count", plus _datasheet_guidance + _datasheet_parts.

pcb_move_componentsA

Move and/or rotate MANY PCB components in ONE IPC round-trip.

PREFER THIS over moving one component per call. Each singular move is a full LLM turn (5-15 s); one call to this tool repositions every component in the list in a single Altium transaction.

Typical uses: applying a full layout pass, running an auto-placement result, undoing and redoing a placement set, adjusting a row of components relative to each other.

Args: moves: List of move dicts. Each dict supports: designator (required) , target component x (optional) , new X in mils y (optional) , new Y in mils rotation (optional) , new rotation in degrees

Example:
    [
      {"designator": "U1", "x": 5000, "y": 5000, "rotation": 0},
      {"designator": "R1", "x": 5200, "y": 4800},
      {"designator": "C1", "rotation": 90},
    ]

Returns: Dictionary with per-designator results and a count.

pcb_import_placementA

Position components from a coordinate list (pick-and-place import).

The inverse of pcb_export_coordinates. Each entry repositions a component already on the board, by designator. Absolute coordinates in mils, rotation in degrees; supplying side/layer flips the component to that side.

Args: placements: list of dicts, each supporting: designator (required) x (optional) , X in mils y (optional) , Y in mils rotation (optional) , degrees side (optional) , "top" or "bottom" layer (optional) , "TopLayer" / "BottomLayer" (overrides side)

Returns: {"applied": N, "failed": M}.

pcb_add_teardropsA

Add teardrops to pad/via-track junctions board-wide.

Launches Altium's Teardrop command on all objects. The Teardrop dialog is modal and cannot be suppressed from script (same limitation as the ECO dialog): choose Add and confirm it in Altium. Returns once the command is dispatched.

pcb_remove_teardropsA

Remove teardrops board-wide.

Opens the same modal Teardrop dialog as pcb_add_teardrops; choose Remove and confirm it in Altium.

pcb_autoplace_silkscreenA

Move designators that break silk clearance to a clear position.

A visible designator that is closer than the silk clearance to anything else on its overlay layer (component outlines, texts, fills), closer than the mask clearance to a pad's mask opening on its side, or off the board, is tried at a ring of auto-position anchors and left on the first that clears. A designator that is already clear is not touched, and one no anchor clears goes back where it was and is listed in unplaced_designators for a person. First-fit on bounding rectangles, so it errs towards "blocked"; run DRC after.

Args: silk_clearance_mils: Silk to silk clearance. None reads the board's Silk To Silk rule. mask_clearance_mils: Silk to solder mask clearance. None reads the board's Silk To Solder Mask rule. mask_expansion_mils: How far a pad's mask opening extends past its copper (default 4, Altium's default). designators: Only these components. None means all.

Refused with NO_CLEARANCE, moving nothing, when a clearance is neither given nor readable from an enabled rule.

Returns: placed, already_clear, unplaced, hidden, total, the placed and unplaced designators, and each clearance used with its source (argument or the rule's name). skipped is unplaced + hidden.

pcb_tune_lengthA

Add approximate routed length to a net with a square serpentine.

Lays a square meander on net at (x_mils, y_mils) sized to add about add_length_mils, then reports the net's routed length before and after.

Open-loop and NOT DRC-checked: you choose where the meander goes and verify clearance afterward (pcb_run_drc). Use it to get a net close to a target, then finish in Altium's interactive tuner if needed. There is no scriptable interactive tuner in Altium, so this is the available approximation.

Args: net: net name. add_length_mils: target length to add (mils). x_mils, y_mils: anchor point of the meander (mils). layer: routing layer (default TopLayer). amplitude_mils: meander height; bump count = add_length / (2*amplitude). width_mils: track width.

Returns: {"length_before_mils", "length_after_mils", "added_mils", "bumps", "drc_checked": false}.

pcb_panelizeA

Build a production panel on the CURRENT (blank) PCB document.

Open a new empty .PcbDoc and run this on it. Creates an embedded-board array of child_path, a rectangular panel outline, corner tooling holes, and fiducials. Supply the source board size (board_width_mils / board_height_mils).

Args: child_path: path to the source .PcbDoc to array. board_width_mils, board_height_mils: source board size (mils). rows, cols: array dimensions. col_gap_mils, row_gap_mils: spacing between adjacent boards. border_mils: rail width around the array (also half the tooling inset). tooling_holes, fiducials: whether to add them.

Returns: {"panel_width_mils", "panel_height_mils", "rows", "cols", "tooling_holes", "fiducials"}.

pcb_delete_invalid_objectsA

Remove degenerate primitives from the active PCB.

Deletes zero-length tracks and zero-area regions (board hygiene that accumulates from edits and imports). Returns the count removed.

Returns: {"removed": N}.

pcb_audit_pad_center_connectedA

Find pads whose center has no copper entering it (acid-pad check).

For every netted pad, checks that a track / arc / via on the same net reaches the pad centroid. A pad connected only at its edge (an "acid pad") can fail in fab. Read-only; reports offenders with designator, pad, net, and location.

Returns: {"checked": N, "offenders": M, "items": [...]}.

pcb_auto_size_board_outlineA

Fit the board outline around the embedded-board array(s) plus a margin.

Use after pcb_panelize (or any embedded-board layout) to size the panel outline to its content. Unions the embedded boards' extents, adds margin_mils on every side, and converts the result to the board outline.

Args: margin_mils: border added on each side (default 100).

Returns: {"width_mils", "height_mils"}.

pcb_normalize_viasA

Set free vias to their Routing Via rule's sizes, or to given ones.

Without size_mils/hole_mils, each via takes its dominant Routing Via rule's preferred diameter and hole. A rule in template mode ("Templates Used To Check Via") is refused for its vias: its size fields are not what it checks, and reading them once set nearly every via on a board to a pad no larger than its hole. Use pcb_apply_via_template for a template rule. Any target without an annular ring is refused. Refused vias are left as they were.

Args: size_mils, hole_mils: An explicit diameter and hole, together. net: Only vias on this net. from_size_mils, from_hole_mils: Only vias at this size now (within 0.05 mil), e.g. to fix one group. dry_run: Report what would change and change nothing.

Returns: changed, unchanged, failed (did not read back), the refusal counts, and before/after: vias counted by "diameter/hole" in mils. success is false when any via was refused or failed.

pcb_apply_via_templateA

Make free vias match a source via: its via template, hole and diameter.

Point at a via that already uses the template you want (set one in Altium's Properties panel if none does), and every other free via, or those matching the filters, takes its template link, mode, hole and diameter. This is how to bring a board in line with a Routing Via rule in template mode. Altium Designer 22 or later.

The template link is copied the way Altium's own FormatCopy script does it. Diameter and hole are read back; the link has no member to read back, so check one via's template in the Properties panel afterwards. Growing vias can create clearance violations: run DRC with the rules enabled.

Args: source_x, source_y: A point on the source via, in mils. net: Only vias on this net. from_size_mils, from_hole_mils: Only vias at this size now. dry_run: Report what would change and change nothing.

Returns: source (position, size, hole, net), changed, failed, before/after counted by "diameter/hole" in mils. Refused with LOCAL_STACK for a source with a per-layer stack and NO_ANNULAR_RING for a source without a ring.

pcb_copy_designators_to_mechB

Copy each component designator onto a mechanical layer (assembly prep).

Places a .Designator special-string text on layer over every component, for an assembly drawing that survives silkscreen edits.

Args: layer: target mechanical layer (default Mechanical1).

Returns: {"copied": N, "layer": "..."}.

pcb_trim_extend_trackA

Trim or extend one track endpoint along the track's own slope.

Finds the track endpoint nearest (from_x, from_y), then slides it to the perpendicular projection of (to_x, to_y) onto that track's line. The track stays collinear (no bend introduced) and the opposite end stays put, so this cleanly lengthens a track up to a target or pulls it back. All coordinates in mils.

Args: from_x, from_y: Picks which endpoint moves (nearest one wins). to_x, to_y: Target the moving end is projected toward. tolerance_mils: How close (from_x, from_y) must be to a real endpoint (default 5).

Returns: {"moved_end", "old_x", "old_y", "new_x", "new_y", "layer"}.

pcb_cleanup_tracksA

Tidy stray track geometry: delete slivers and/or merge collinear runs.

Modes:

  • "slivers" (default): delete every track shorter than min_length_mils. Catches the zero/near-zero stubs left behind by editing.

  • "merge": join two collinear, same-layer, same-width, same-net tracks that meet end to end into a single track. The merge only fires when the shared point is a clean degree-2 junction (exactly those two track ends, with no via, pad, arc, or third track present), so it is safe to run on routed copper without breaking connectivity.

  • "both": slivers pass, then merge pass.

Args: mode: "slivers", "merge", or "both" (default "slivers"). min_length_mils: Sliver threshold (default 1).

Returns: {"slivers_deleted", "merged", "mode"}.

pcb_unrouteA

Take up the focused board's routing: its tracks, arcs and vias.

Removes the free tracks and arcs on signal layers, and the free vias, that carry a net. Never removes a footprint's own copper, polygons or their hatching, dimensions, keepouts, or copper with no net (an antenna, a logo, a heat spreader): those are drawn, not routed. Locked routing stays unless include_locked, since locking is how a designer keeps a route. Polygons are not repoured; pcb_repour_polygons does that.

Refuses unless the focused board is expect_file. With checkpoint (default), first snapshots the board's folder as it is ON DISK, so it can be restored with app_restore_checkpoint; unsaved edits in the editor are not in it, so save first if there are any.

For other selections of board copper, obj_delete and obj_batch_delete take a board type with a filter, e.g. eTrackObject with Layer=TopLayer|InComponent=false.

Args: expect_file: full path of the board to un-route. nets: take up only these nets' routing. None takes every net's. A name that is not a net on the board refuses the whole call and nothing is removed. include_locked: take locked routing as well (default False). checkpoint: take a checkpoint first (default True).

Returns: {"tracks", "arcs", "vias", "nets", "kept_locked", "failed", "checkpoint"}: how many of each were removed, how many nets they were on, and how many locked primitives stayed. Or {"error": ...}.

pcb_place_thieving_padsA

Fill bare copper area with a grid of isolated thieving pads.

Drops a regular grid of small round pads on layer, inside the board outline minus margin_mils. A grid point is skipped whenever any existing primitive (track, arc, via, pad, fill, region, polygon, or component body) sits within half-pad + clearance of it, so pads land only in genuinely empty regions. Pads carry no net. Evens out plating current on sparse boards. Capped at 5000 pads per run.

Args: layer: Copper layer to populate (default TopLayer). pad_size_mils: Pad diameter (default 20). pitch_mils: Grid spacing (default 50). clearance_mils: Keep-out from existing copper (default 15). margin_mils: Inset from board edge (default 100).

Returns: {"placed", "scanned", "layer"}.

pcb_move_tracks_to_layerA

Move all tracks of a net to one layer, adding vias where needed.

Relayers every track of net_name onto target_layer, then drops a via at each same-net single-layer (SMD) pad that is NOT on the target layer, since that pad can now only connect through a layer change. Multilayer (through-hole) pads need no via. This flattens a net's routing onto one layer, so use it deliberately.

Args: net_name: Net whose tracks move. target_layer: Destination signal layer (e.g. "BottomLayer"). via_size_mils: Via pad diameter for added vias (default 50). via_hole_mils: Via hole diameter (default 28).

Returns: {"net", "target_layer", "moved", "vias_added"}.

pcb_bevel_polygon_cornersA

Chamfer the corners of a copper polygon pour.

Replaces each sharp vertex with two points set back along its edges by bevel_mils (auto-clamped so neighbouring bevels never overlap), giving every corner a straight 45-style cut. Only straight-outline polygons are handled, a polygon containing arc segments is left untouched. The polygon is repoured after the edit.

Args: index: Which polygon to bevel (0-based, in board iteration order). net_name: If set, count only polygons on this net when applying index. bevel_mils: Set-back distance per edge (default 25).

Returns: {"beveled", "index", "orig_vertices", "new_vertices", "bevel_mils"}, or an error code (NOT_FOUND / BAD_POLYGON / HAS_ARC).

pcb_plan_placementA

Connectivity-driven auto-placement: shorten wirelength, keep components legal. Dry-run by default -- returns a proposed move list and quality metrics without touching the board.

A pure-Python solver run on the current board. It reads the compiled netlist and every component's real footprint bounding box, then improves positions to minimize half-perimeter wirelength (HPWL) while keeping components inside the board outline and free of same-layer overlaps.

Two solver cores are available via engine:

  • "refine" (default): a force-directed relaxation that nudges the board's CURRENT positions, then a legalization pass. Best for tidying an already-placed board.

  • "construct": a from-scratch constructor that sizes the placement, seeds parts at connectivity-weighted centroids, chooses orientations, legalizes, and runs a short cooled polish on a full multi-term objective (wirelength, overlap, board-edge, decoupling proximity, connector banding, thermal). Best for an unplaced or badly scrambled board. The full per-term objective is always returned under objective_report.

With optimize_rotation (default on) it also picks each eligible part's orientation (0/90/180/270) to point its pins at the nets they connect -- the classic auto-place win for 2-pin passives. This needs per-pin geometry, so the tool also reads the board's pads and maps them to components; orientation is only considered for top-side parts currently sitting at an orthogonal angle (others keep their rotation).

Workflow: call once with apply=False (default), read the hpwl_improvement_pct and overlap_pairs_after, eyeball the moves, then call again with apply=True to commit them in one pcb.batch_move_components transaction. Components on opposite layers may share an X/Y footprint (they cannot physically collide).

Seeding: by default the solver refines the board's CURRENT positions (small, sensible nudges). Pass reseed_grid=True for a from-scratch re-place (ignores current positions) -- use only on an unplaced or badly-scrambled board.

Args: designators: If given, ONLY these components are moved; every other placed component is held fixed but still attracts its nets and blocks overlaps. If omitted, all components are candidates (minus fixed). fixed: Designators to pin in place (connectors, mounting holes, anything already positioned). Naming-agnostic -- you choose which; the tool does not guess by prefix. region: Placement bounds {x1, y1, x2, y2} in mils. If omitted, the board outline's bounding rectangle is used. iterations: Global-placement relaxation steps (default 400). More iterations = better convergence, longer runtime. grid_mils: Snap grid for final positions (default 5). clearance_mils: Extra copper-to-copper breathing room added to each pair's half-extents (default 15). max_net_fanout: Skip nets touching more than this many components (default 0 = keep all). Set e.g. 20 to ignore power/ground planes that do not usefully guide placement. exclude_nets: Explicit net names to ignore (e.g. ["GND"]). critical_nets: Net names to treat as placement-critical (e.g. a clock, a high-speed pair, a sensitive analog feedback line). Their pull is scaled by critical_weight so the solver keeps their endpoints close and shortens those traces first, at a small cost to ordinary nets. You (the planner) decide which nets matter; the tool does not guess by name. critical_weight: Multiplier applied to critical_nets pull (default 3.0, clamped to [1, 20]). Higher = more aggressive shortening of the critical nets versus the rest. edge_parts: {refdes: band} mapping parts that must sit on a board EDGE to which edge -- "L"/"R"/"T"/"B" (left/right/top/bottom). The solver seats each against that edge (centred on the free axis) and holds it there. Use for connectors, mounting holes, USB/headers -- anything needing cable or panel access. You (the planner) decide which; the tool does not guess by designator prefix. keepout_groups: {refdes: group} mixed-signal separation tags. Parts in DIFFERENT groups are pushed apart (the industrial-engineering "undesirable adjacency" relationship) -- segregate a noisy switching / digital section from a sensitive analog / RF one. The group string is opaque (only equality matters); you (the planner) assign the domains semantically. construct engine only. Separating the groups usually also shortens wirelength by de-interleaving them. match_groups: {refdes: group} matched-pair keep-together tags. Parts in the SAME group are pulled ADJACENT even when they share no net -- the absolutely-necessary-adjacency relationship / analog common-centroid matching (a differential pair's two input resistors, a current mirror, a matched array). HPWL only co-locates parts that share a net; this co-locates matched ones that don't. construct engine only. match_roles: {refdes: role} common-centroid sub-device labels WITHIN a match_group (e.g. A/B for the two matched halves of a cross-quad or interdigitated array). The optimiser drives each role's centroid onto a common point so a linear process/thermal gradient cancels -- it picks the balanced ABBA/ABAB arrangement that match_groups alone (which only keeps the cells compact) cannot distinguish from an unbalanced AABB. Only meaningful with >= 2 roles per group and >= 2 cells per role; needs match_groups set too. construct only. plan_json: The DesignPlan (JSON string or dict) you authored. When given, the placer auto-derives matched-pair match_groups from differential nets and analog/digital keepout_groups from the plan's net roles, and MERGES them under the explicit match_groups / keepout_groups args (explicit wins per refdes). No-op for a plan with no differential / mixed-signal structure. What was inferred is echoed in auto_constraints. reseed_grid: Re-place from a fresh grid instead of refining current positions (default False). optimize_rotation: Also choose part orientation to shorten pin-level wirelength (default True). Set False to keep every part's current rotation. engine: "refine" (default, nudge current positions) or "construct" (from-scratch placement). "construct" implies a from-scratch re-place regardless of reseed_grid. restarts: "construct" engine only. When > 1, run that many seeded restarts and keep the lowest-objective legal placement (variance reduction at a cost linear in restarts). Default 1 (single deterministic run). render_png: Optional file path. When set, also render a preview image of the proposed placement (board outline, courtyards, net stars) to that path and return it as preview_png (offline, matplotlib). Rendering never breaks the move data; failures surface as preview_error. apply: When True, commit the moves to the board. Default False (dry-run).

Returns: Dict with: - dry_run: bool (True unless apply and moves exist) - engine: the solver core used - component_count, movable_count, fixed_count - net_count (nets used after filtering) - pin_count (pads mapped to components for rotation) - summary: a one-line plain-language assessment synthesising legality, utilization, signal routability, decoupling, and the suggested board size -- read this first to judge the placement - hpwl_before, hpwl_after, hpwl_improvement_pct - overlap_pairs_before, overlap_pairs_after - objective_report: every objective term un-weighted (hpwl, via, cong, clear, edge, decap, conn, therm) plus weighted_total, legal, and utilization for the proposed layout - net_length_report: longest_nets (the few nets with the largest physical bounding span in mils -- the routing risk, and the candidates to mark critical_nets) plus critical_net_spans echoing the achieved span of each net you flagged critical, so you can confirm it paid off - ratsnest: routability / via-pressure indicator from the straight-line MST of each net. signal_crossings (rails above signal_fanout_cap pins excluded, since planes carry no signal via) predicts routing difficulty -- different-net MST edges that cross need a layer change; total_crossings is the conservative all-net count - decoupling_report: the engine's structural decoupling analysis -- a list of {decap, ic, distance_mils} (worst first) naming which cap decouples which IC (found on the connectivity graph, never by reference) and how close it landed to the served power pin. Empty when no decoupling is structurally identifiable - suggested_board: for the construct engine, the board rectangle it sized and tightened to the placement ({x1, y1, x2, y2, width, height} in mils) -- the "best PCB size" recommendation. null for the refine engine, which works inside the existing outline - moved_count, rotated_count and moves (each {designator, from: {x, y, rotation}, to: {x, y, rotation}} in mils/degrees; to.rotation is null when the orientation was unchanged) - region used, notes from the solver, and apply_result when apply=True.

pcb_copy_component_placementA

Clone placement from source components onto destination components.

For each src -> dst pair, copy the source's PCB placement (layer, X, Y, rotation) onto the destination. With the optional flags also clone the designator-text placement (X/Y offset from component centre, rotation, size, width, layer, on/off) and the comment-text placement.

Real-world use: a board with N identical channels (filter / amp / switching stages). Lay out channel 1 once, then call this tool with a mapping like {"R1": "R11", "C1": "C11", "U1": "U2"} and channel 2 picks up channel 1's layout instantly. Saves the manual click-and-drag-drag-drag of replicating identical layouts.

Mapping-driven rather than relying on sort-order matching of selections -- the agent-callable equivalent of a manual replicate.

Args: mapping: {source_designator: dest_designator}. Each source must already be placed somewhere; each dest gets overwritten with the source's placement. include_designator: Also copy designator-text placement (offset, rotation, size, layer, visibility). Default True. include_comment: Also copy comment-text placement. Default True.

Returns: Dict with: - applied: pairs that succeeded - failed: pairs that failed (src/dst missing or apply exception) - items: per-pair {src, dst, ok, error}

pcb_replicate_layoutA

Replicate a routed channel's ROUTING onto a matching channel.

Where pcb_copy_component_placement only relocates components, this copies the source group's routing -- tracks, arcs, vias, polygons, regions, fills -- onto the destination group and remaps each copy's net from the source net to the corresponding destination net. The classic multi-channel "lay out channel 1, replicate to channels 2..N" workflow, end to end.

Positioning: one rigid transform is derived from the FIRST mapping pair (the anchor) -- the copied routing is rotated by dstRot - srcRot about the source anchor, then translated by dstAnchor - srcAnchor, so it lands on the destination components where they already sit. The destination components are left in place unless move_components=True.

Which routing is copied (naming-agnostic): routing on nets INTERNAL to the source group -- every component pad on the net belongs to a mapped source component. Nets that escape the group (a shared GND / power pour) are deliberately left alone. Pass an explicit nets list to override and copy exactly those.

Preconditions: the destination components must already be placed congruently with the source (e.g. run pcb_copy_component_placement first, or pass move_components=True). congruence_warnings in the result counts destination parts that do not match the anchor transform -- their routing may not line up.

Args: mapping: {source_designator: dest_designator}. The first pair is the transform anchor; include every component in the channel so its internal nets are recognised. nets: Optional explicit source net names to copy, instead of the internal-net auto-detection. move_components: Also relocate the destination components onto the rigid transform, guaranteeing the routing aligns. Default False (destination stays where it is).

Returns: Dict with: - copied: routing primitives replicated - net_assigned: copies given a destination net - internal_nets: source nets selected for copying - shared_nets_skipped: group nets left alone (escape the group) - congruence_warnings: destination parts off the anchor transform (routing may not align) - notes: any caveats

pcb_filter_variant_componentsA

Select a variant's components of one fitted-class on the board.

Classifies every component under variant_name (fitted original / alternate / not fitted) and selects exactly the chosen class on the active PCB, deselecting the rest -- so, e.g., the not-fitted parts of a variant stand out for review or for building a component class. The agent-callable equivalent of the community VariantFilter script; selection uses the deterministic component API, not a query process.

Args: variant_name: The project variant to classify against. select: Which class to select -- not_fitted (default), fitted_original, alternate, or all_fitted (fitted_original + alternate).

An unrecognised select is REJECTED rather than guessed. The handler's If chain falls through to not-fitted for any word it does not know, and the reply echoes back the word you sent, so a typo would select the opposite class and report success.

Returns: {"variant", "select", "matched", "designators"}, or {"ok": False, "reason": ...} if select is not one of the four classes, in which case nothing is sent or selected.

pcb_renumber_padsA

Renumber the current PcbLib footprint's pads in spatial order.

The non-interactive form of the community RenumberPads tool: instead of clicking each pad in order, the footprint's pads are sorted by position and assigned sequential designators. Rows/columns are banded by a small tolerance so a grid numbers cleanly. Operates on the active PCB library's current footprint.

Args: order: lr_tb (default) numbers rows top-to-bottom, left-to-right within a row; tb_lr numbers columns left-to-right, top-to-bottom within a column. start: First designator number (default 1). increment: Step between successive pads (default 1). prefix: Optional string prefixed to each number (e.g. A -> A1, A2).

Returns: {"renumbered", "order", "mapping": [{old, new}, ...]} or an error.

pcb_copy_tracks_radialA

Array the selected tracks/arcs/vias radially about a center point.

Replicates the current selection count - 1 more times, each rotated a further angle_step degrees about (center_x, center_y) -- a circular/radial copy. Reuses the same verified Replicate + RotateAroundXY transform as pcb_replicate_layout. Copies are added unselected so the source set stays put.

Args: center_x: Rotation-center X in mils. center_y: Rotation-center Y in mils. count: Total instances including the original (>= 2). angle_step: Degrees between instances. Defaults to 360 / count (a full even ring).

Returns: {"copied", "count", "angle_step"} or an error.

pcb_scaleA

Scale the selected free primitives by a ratio about an anchor.

Each coordinate maps P' = anchor + ratio*(P - anchor) and sizes scale by ratio. Handles free tracks, arcs, vias, pads, fills and text. Primitives inside a component, dimension, or polygon are skipped (as are polygons/regions) -- so this scales free copper and artwork, not footprints. Grounded in the community PCBScale tool.

Args: ratio: Scale factor (> 0). 0.95 shrinks 5%, 1.05 grows 5%. anchor: selection_center (default), board_center, or origin.

Returns: {"scaled", "skipped", "ratio", "anchor_x", "anchor_y"} or an error.

pcb_place_stitching_viasA

Place a grid of stitching vias on a named net within a rectangle.

Standard RF / EMC tool: place GND stitch vias around high-speed traces so the return current has a low-inductance path between reference planes. Also used near connectors / clock generators / EMI-sensitive areas.

Algorithm: walk a regular grid inside the rectangle; for each gridpoint, spatial-iterate within via_size_mils/2 + clearance_mils. Skip the gridpoint if any non-same-net primitive (pad / via / track / arc) is in range. Otherwise create a top→bottom through-via on the target net.

The default is dry-run because mutating a board with N×M new vias is risky. Confirm the placed / skipped count looks right, then call again with dry_run=False.

Args: net: Target net (must already exist on the board). x1_mils, y1_mils, x2_mils, y2_mils: Inclusive rectangle where the grid is placed. PCB origin is bottom-left by Altium convention. spacing_mils: Grid spacing (default 50). For GND stitching near a high-speed bus, /4 of the wavelength of the highest harmonic is a common rule of thumb. via_size_mils: Via pad diameter (default 30). via_hole_mils: Via drill diameter (default 14). clearance_mils: Min gap to any non-same-net existing primitive (default 10). dry_run: When True (default), only count would-be placements without mutating the board.

Returns: Dict with {net, dry_run, placed, skipped, spacing_mils, clearance_mils}.

pcb_calc_impedanceA

Characteristic impedance of a PCB trace via IPC-2141 / Wadell closed-form approximations. Pure math, no Altium hit.

Use this BEFORE routing a high-speed signal to pick the track width that matches your transceiver's target impedance (USB 90 Ω diff, HDMI 100 Ω diff, single-ended PCIe 50 Ω, MIPI 100 Ω diff, etc). For controlled-impedance fab spec sheets, the accuracy of these closed-form formulas is ± ~10% -- good enough for picking widths but fab houses will refine the actual stackup. For real controlled-impedance boards, send the stackup to your fab and use their Polar Si9000 / similar report values.

Chaining with the layer stack: call pcb_get_layer_stackup first to read the actual dielectric heights and εr values from the project, then feed those into this calculator. This is essential -- guessing FR-4 at εr=4.2 ignores the stackup the user actually picked and can give a 15-20% off answer if they're using Megtron 6 or Rogers.

Supported geometries:

  • "microstrip" -- single-ended outer-layer track with reference plane below. IPC-2141 microstrip formula.

  • "microstrip_diff" -- differential pair on outer layer. Adds Wadell's mutual-coupling correction.

  • "stripline" -- single-ended inner-layer track between two reference planes.

  • "stripline_diff" -- differential pair on an inner layer between two reference planes.

Formula sources:

  • IPC-2141 microstrip: Z₀ = (87 / √(εr + 1.41)) × ln(5.98 × h / (0.8 × w + t))

  • Symmetric stripline: Z₀ = (60 / √εr) × ln(4 × b / (0.67 × π × (0.8 × w + t)))

  • Differential variants apply the standard Wadell correction: Zdiff ≈ 2 × Z₀ × (1 - 0.48 × exp(-0.96 × s/h)) for microstrip, similarly with empirical coefficients for stripline.

Args: geometry: One of "microstrip", "microstrip_diff", "stripline", "stripline_diff". width_mils: Track width in mils. For differential, this is the width of EACH conductor (not the pair). dielectric_height_mils: For microstrip, the dielectric thickness BELOW the trace (track-to-reference-plane). For stripline, this is the TOTAL dielectric thickness between the two reference planes (b in IPC). dielectric_constant: εr of the dielectric. Common: 4.2 (FR-4 at 1 GHz), 3.5 (Megtron 6), 3.0 (Rogers 4350B), 2.2 (PTFE). Default 4.2. copper_oz: Copper weight in oz/ft² (used for t, trace thickness). 1 oz = 1.378 mils. Default 1.0. spacing_mils: Edge-to-edge gap between conductors of a differential pair, in mils. Ignored for single-ended.

Returns: Dict with: - geometry, width_mils, dielectric_height_mils, dielectric_constant, thickness_mils, spacing_mils (when diff) - z0_ohms: single-ended characteristic impedance - zdiff_ohms (diff geometries only): differential pair impedance - propagation_delay_ps_per_inch: propagation delay, derived from the effective εr (microstrip uses εr_eff = (εr + 1)/2; stripline uses εr directly).

pcb_calc_trace_width_for_impedanceA

Trace WIDTH to hit a target impedance (inverse of pcb_calc_impedance).

The design-time complement: instead of "what impedance does this width give?", it answers "I need 90 ohm USB / 100 ohm HDMI differential, or 50 ohm single-ended; how wide?". Inverts the same IPC-2141 / Wadell closed forms, so it round-trips with the forward calc. Pure math, no Altium. Same +/-10 % caveat -- a fab field solver refines the stackup; read the real dielectric heights / er with pcb_get_layer_stackup.

Args: target_ohms: Single-ended Z0 for microstrip/stripline, or the differential Zdiff for microstrip_diff/ stripline_diff. geometry: One of microstrip, microstrip_diff, stripline, stripline_diff. dielectric_height_mils: Trace-to-plane height (microstrip) or full between-plane thickness (stripline). dielectric_constant: er (default 4.2 FR-4). copper_oz: Copper weight for trace thickness (default 1.0). spacing_mils: Edge-to-edge gap (required for *_diff).

Returns: {"ok": True, "width_mils", "single_ended_z0_ohms", "feasible", ...} (feasible False when the target needs a width <= 0: raise the dielectric height or lower the target), or {"ok": False, ...}.

pcb_calc_terminationA

Decide if a net needs termination and size the resistor(s).

Two questions the impedance calc does not answer: is this net electrically long for its edge rate, and -- if so -- what termination value? A net stays a lumped wire while its one-way flight time is under length_fraction of the rise time (Johnson & Graham's 1/6 rule by default; pass 1/2 for the looser convention); past the critical length the reflection arrives during the edge and the net must be matched to z0_ohms. Pure math, no Altium. Propagation speed uses the effective dielectric constant (Er for stripline; reduced for microstrip, Hammerstad when width_mils/dielectric_height_mils are given, else the 0.475*Er+0.67 approximation).

Args: length_mils: Routed (or estimated) net length. rise_time_ns: Signal edge rate (the driver's 10-90 % rise time). z0_ohms: Line characteristic impedance (size it with pcb_calc_impedance; default 50). dielectric_constant: er (default 4.2 FR-4). geometry: microstrip or stripline. driver_impedance_ohms: Driver output impedance, subtracted for the series value (Rs = Z0 - Rdriver). vcc: Supply rail; enables the Thevenin split option when > 0. width_mils, dielectric_height_mils: Microstrip geometry for the Hammerstad Er_eff (optional; falls back to the approximation). length_fraction: Electrically-long threshold (default 1/6). multi_load: True for a bus / multi-receiver net (recommends far-end Thevenin/parallel instead of source series).

Returns: {"ok": True, "needs_termination", "recommended", "critical_length_mils", "flight_time_ns", "options": {...}, "summary"} -- options carries the ideal and nearest-E24 resistor values (and the AC cap) for every applicable scheme.

pcb_calc_length_matchA

Length-match tolerance and serpentine compensation for a bus / pair.

Turns a timing budget into the length-match window the routing must hold, and -- given the routed lengths -- the serpentine copper each net needs. The complement of Altium's pcb_tune_length (which executes the meander) and pcb_get_trace_lengths (which reads lengths back). Pure math, no Altium. Rests on the ns/in == ps/mil identity, so skew maps to length exactly; propagation speed uses the effective dielectric constant (stripline = Er, the usual inner-layer bus case; microstrip reduced).

The skew budget is taken from skew_budget_ps if > 0, else from match_fraction * rise_time_ns (the "match to within 10-20 % of the edge" rule).

Args: lengths: Optional {net: length_mils} of routed lengths; when given, returns a per-net match report targeting the longest net. skew_budget_ps: Allowed skew in ps (e.g. a DDR byte-lane budget). rise_time_ns: Edge rate, used with match_fraction if no ps budget. match_fraction: Fraction of the rise time allowed as skew (default 0.1). dielectric_constant: er (default 4.2 FR-4). geometry: stripline (default) or microstrip. width_mils, dielectric_height_mils: Microstrip geometry for the Hammerstad Er_eff (optional).

Returns: {"ok": True, "er_eff", "t_pd_ps_per_mil", "skew_budget_ps", "tolerance_mils", "members"?: [{net, length_mils, mismatch_mils, skew_ps, compensation_mils, within_tolerance}], "target_length_mils"?, "worst_skew_ps"?, "all_matched"?, "summary"}.

pcb_calc_thermal_viasA

Size a thermal-via field under a power pad (Fourier conduction).

The thermal calcs answer "what junction-to-ambient resistance does the part need" (required_theta_ja); this answers "how many vias get the heat into the board". A plated via is a copper tube of axial resistance R = L / (k * A); vias conduct in parallel (R_array = R/N). Give a target -- target_k_per_w directly, or power_w with a delta_t_c budget (target = dT/P) -- to solve the count, or an explicit via_count to score a layout. Pure math, no Altium. Models the via field's conduction bottleneck and assumes good copper spreading on the pad and the receiving plane (a thin-copper spreading term is left to a field solver).

Args: drill_mm: Finished via drill diameter. board_thickness_mm: Conduction length -- top layer to the heat- The EasyEDA and KiCad builds spell this length_mm; either is accepted. length_mm: Alias for board_thickness_mm. spreading plane (full thickness for a bottom plane). plating_um: Barrel plating thickness (default 25 = 1 oz). filled_copper: True for a copper-filled via (full-circle copper), False for an unfilled / resin-filled barrel (annulus only). k_cu: Copper thermal conductivity, W/(m.K) (default 385). power_w: Dissipation, for the target (with delta_t_c) and the realized temperature rise. delta_t_c: Allowed rise across the via field. target_k_per_w: Explicit target array resistance. via_count: Score this exact count instead of solving one.

Returns: {"ok": True, "via_count", "single_via_k_per_w", "array_k_per_w", "target_k_per_w", "temp_rise_c", "barrel_area_mm2", "summary"}.

pcb_calc_track_current_capacityA

IPC-2221 current-carrying capacity for a PCB track.

Pure math, no Altium hit -- safe to call without an attached session. Returns the current the track can sustain at each of several temperature rises (1, 5, 10, 20, 30 °C above ambient), plus DC resistance if a length is supplied.

Formula (IPC-2221, also IPC-2152's curve-fit predecessor): I = k × (ΔT)^0.44 × (h × w)^0.725 where h and w are in mils, ΔT in °C, and: k = 0.048 for external layers (top, bottom) k = 0.024 for internal layers (mid) Internal-layer derating is the dominant correction; running a signal that draws 5 A on TopLayer might be fine but on an inner plane the same width only handles ~3 A at the same ΔT because heat dissipates more slowly.

Copper thickness conversion: 1 oz/ft² ≈ 1.378 mils. So copper_oz=1.0 → h = 1.378 mils, copper_oz=2.0 → h = 2.756 mils.

Resistance: R = ρ × L / (h × w) with ρ = 6.7 × 10⁻⁷ Ω-mil (annealed copper, 25 °C). Multiply current × resistance to get voltage drop across the track.

Exposes the track-current math so the agent can apply it across an entire power net after pulling track widths from pcb_get_trace_lengths.

Args: width_mils: Track width in mils. copper_oz: Copper weight in oz/ft². Common values: 0.5 oz (HDI inner), 1.0 oz (standard), 2.0 oz (power planes), 3.0 oz (heavy-current). layer: "external" (top / bottom) or "internal" (mid / inner planes). Defaults to external. length_mils: If > 0, also returns DC resistance and voltage drop at each temperature rise.

Returns: Dict with: - width_mils, copper_oz, thickness_mils, layer - current_amps (dict, keys 1c, 5c, 10c, 20c, 30c) - resistance_mohm (only if length_mils > 0) - voltage_drop_mv (only if length_mils > 0, dict keyed same as current_amps)

pcb_calc_trace_width_for_currentA

Minimum track WIDTH to carry a target current (inverse IPC-2221).

The design-time complement of pcb_calc_track_current_capacity: instead of "what current does this width carry?", it answers "I need N amps, how wide?". Pure math, no Altium hit.

Solves I = k * dT^0.44 * (h*w)^0.725 for w with the same constants as the forward tool (so they round-trip), k = 0.048 external / 0.024 internal, h = copper_oz * 1.378 mils.

Args: current_amps: Target current the track must carry. The EasyEDA and KiCad builds spell this current_a; either is accepted here, so one call works on any backend. current_a: Alias for current_amps. copper_oz: Copper weight, oz/ft^2 (0.5 / 1.0 / 2.0 / 3.0). delta_t_c: Allowed temperature rise above ambient (10 degC is a common conservative budget; 20-30 degC for tight boards). layer: "external" (top/bottom) or "internal" (inner; needs ~2.6x the width for the same rise). margin: Fractional widening above the bare IPC minimum for the recommendation (default 0.2 = 20 %). length_mils: If > 0, also returns DC resistance and voltage drop at the recommended width.

Returns: {"ok": True, "min_width_mils", "recommended_width_mils", "copper_oz", "delta_t_c", "layer", ...optional resistance/drop} or {"ok": False, "reason": ...}.

pcb_add_testpoints_for_net_classA

For each net in a netclass that does NOT already have a testpoint, place a new pad above the board outline with the net assigned and the standard IsTestpoint / IsAssyTestpoint flags set.

Typical workflow:

  1. Set up a netclass in Altium called TestPoints and add the signals you want probed (clocks, power rails, reset, debug signals).

  2. Call this tool with net_class="TestPoints" and the pad geometry your bed-of-nails fixture requires.

  3. Open the PCB; the new pads sit in a row above the board, with the right net + testpoint flags already set.

  4. Drag them into their final positions (or run pcb_move_components if you've pre-computed where they go).

Coverage detection: a net is treated as "already covered" if any pad or via on it carries ANY of the four testpoint flags (so DFM tools, fab-side bed-of-nails vendors, and assembly- side probe vendors all see the same coverage). Pass force=True to override and place anyway.

Agent-friendly: no GUI, results returned for follow-up.

Args: net_class: Netclass name to scan. type: "smd" (top-layer SMD pad, no hole) or "through_hole" (multi-layer pad with drill). pad_size_mils: Outer pad size in mils. Default 40. hole_size_mils: Drill diameter, only used for through-hole testpoints. Default 20. fab_top: Set IsTestpoint_Top (top-side bed-of-nails). fab_bottom: Set IsTestpoint_Bottom. assy_top: Set IsAssyTestpoint_Top (top-side assembly probe). Default True. assy_bottom: Set IsAssyTestpoint_Bottom. force: Ignore existing testpoint coverage and always place a new pad.

Returns: Dict with {net_class, type, placed, skipped_already_covered, placed_nets[], skipped_nets[]}.

pcb_apply_dnp_paste_exclusionA

Suppress stencil paste on Not-Fitted (DNP) components.

The remediation half of audit_variant_not_fitted. A Not-Fitted component is on the BOM as a placeholder and must NOT receive paste: the SMT line would otherwise deposit paste on empty pads, and the bridging that follows shows up as rework.

With no designators this asks audit_variant_not_fitted for the components Not Fitted in the project's CURRENT variant, so the selection follows the variant you actually have open. Pass an explicit list to override that.

Mechanism: each surface pad gets a manual PasteMaskExpansion of minus its larger dimension, which collapses the stencil aperture whatever the pad shape. Through-hole pads are left alone and counted separately, since they have no aperture to suppress.

Reversible: restore=True discards the manual override so Altium recomputes the expansion from the design rules.

RESTORE THE SAME LIST YOU APPLIED. restore will NOT guess: called without designators it refuses and tells you what to pass. That is deliberate. Resolving a restore from the current variant means excluding under one variant, switching, then restoring, and leaving any component that was excluded but is fitted in the new variant with its aperture still suppressed. Nothing would report it: the call succeeds, the pad looks ordinary in the editor, and the part comes back from assembly unsoldered.

The apply reply lists every component it touched in items. Pass those designators back to restore. If you really do want the current variant's Not-Fitted list, ask for it with use_current_variant=True and you own the choice.

Apply is unaffected: with no designators it uses the current variant's Not-Fitted list, which is the whole point of applying.

Mismatch is visible either way. components_requested versus components_matched in the reply reports how many of the names you passed were actually found on this board.

Args: designators: Components to treat. Omit on an apply to use the current variant's Not-Fitted list. Required on a restore unless use_current_variant is set. restore: Undo a previous exclusion instead of applying one. dry_run: Report which components WOULD be treated and change nothing. Worth using first: this edits the board, and a wrong variant selection would strip paste off parts that are meant to be fitted. use_current_variant: Allow a restore with no designators to resolve from the variant open right now. Off by default because that is the failure above.

Returns: Dict with restored, components_requested, components_matched, pads_changed, pads_skipped_through_hole and per-component items. In dry-run mode: dry_run, designators and source. On a refused restore: {"ok": False, "reason": ...} and nothing is read from or written to the board.

pcb_make_paste_gridA

Split a single pad's solder-paste opening into a grid of smaller fills. The classic use case is the central thermal pad on a QFN / DFN / QFP -- a full-area paste opening makes the IC "swim" sideways during reflow as the molten solder pool reduces friction. Splitting the opening into smaller squares totalling ~50-75% coverage gives the IC something to bond to while letting flux gases escape.

Standard datasheet recommendations:

  • QFN / DFN exposed pad: 50-75% coverage, 0.3-0.5 mm squares with ≥ 0.2 mm gap (≈ 15 mils / 8 mils)

  • QFP heat-spreader: 60-70% coverage

  • Large connector body pads: 60% is usually enough

ALWAYS consult the IC manufacturer's recommended stencil pattern before defaulting to this script -- some packages specify a star, diagonal, or windowpane pattern; this tool only does a regular grid.

Args: designator: Component reference designator, e.g. "U5". pad_name: Pad identifier on that component. For QFN / DFN exposed pads this is usually "0" or the largest-numbered pad. Check the footprint first. min_grid_size_mils: Starting grid block size, in mils. Bumped up in 5-mil steps if coverage % is below target. Default 15 mils (≈ 0.38 mm). min_gap_mils: Minimum spacing between grid blocks, in mils. Default 5 mils (≈ 0.13 mm). min_coverage_pct: Target paste coverage as a percentage of pad area, 0-100. Default 60. Iterates grid size upward until met.

Returns: Dict with {designator, pad_name, grid_x, grid_y, grid_size_mils, gap_x_mils, gap_y_mils, fills_placed, coverage_pct}. The existing full-area paste opening is suppressed via PasteMaskExpansion = -pad_dimension on the pad cache before the grid fills are laid down.

pcb_get_differential_pairsA

Enumerate every IPCB_DifferentialPair on the active PCB and report each pair's two halves with length skew.

Length mismatch between the two halves of a diff pair is one of the most common high-speed routing bugs. Transceiver specs:

  • USB 2.0: skew < 150 mils

  • USB 3.x SuperSpeed: skew < 5 mils per pair

  • HDMI: skew < 200 mils within pair, < 800 mils between

  • MIPI D-PHY (high-rate): skew < 5 mils

  • PCIe Gen3+: skew < 5 mils within lane

  • Ethernet PHY: typically < 50 mils Catching skew violations pre-fab saves a respin -- the agent should call this near the end of a layout review.

Returns one row per pair with:

  • name: pair name (e.g. "USB_DATA" if the SCH had .DiffPair directives USB_DATA_P / USB_DATA_N)

  • positive_net / negative_net: the two member nets

  • pos_length_mils / neg_length_mils: summed track + arc length for each half (signal layers only)

  • skew_mils: absolute difference, the number you compare against the transceiver spec

  • both_routed: false means one half is still ratsnest- only (lengths are unreliable in that state)

Uses the PCB API IPCB_DifferentialPair interface.

Returns: Dict with {count, pairs: [...]}.

pcb_clear_source_footprint_libraryA

Clear Comp.SourceFootprintLibrary on board components.

Background: when a project is created from an Integrated Library, every placed component remembers WHICH library it came from. If the user later renames, moves, or consolidates that library, ECO and "Update From Library" start failing with "library not found" warnings because the component still points at the stale path. Clearing SourceFootprintLibrary unpins it so Altium re-matches by library-reference name from whatever's currently in Available Libraries.

This is a standard step when consolidating multiple legacy libraries into a single curated library: drop the new lib into Available Libraries, call this tool, then run an ECO -- the components now bind to the new library.

Generalized with an optional filter.

Args: designator_filter: Restrict to these designators (e.g. ["U1", "U5"]). None / empty clears all components on the board. Match is case-sensitive whole-string.

Returns: Dict with {total, cleared}. total counts components inspected after filter; cleared counts ones that actually had a non-empty SourceFootprintLibrary cleared.

pcb_get_fab_statsA

DFM (Design For Manufacturing) summary -- the numbers fab houses ask for on their quote forms.

One IPC call returns:

  • board_width_mm, board_height_mm, board_area_mm2

  • num_copper_layers

  • vias_total, vias_through, vias_blind, vias_buried

  • pads_plated, pads_unplated, pads_slotted

  • min_annular_ring_mils -- across all plated holes (vias + plated pads). Most fabs need ≥ 4 mil for the standard process; sub-4-mil bumps to HDI.

  • min_track_width_mils -- minimum across all copper layers. Standard fabs offer 4-5 mil; sub-3 mil is HDI.

  • smallest_hole_mils, largest_hole_mils, distinct_hole_count -- the drill bit story. Fab houses charge per distinct drill diameter; collapsing from 7 distinct sizes to 4 can shave noticeable cost.

Useful before sending gerbers to the fab: the agent can spot sub-process-minimum values and warn the user. Aspect-ratio check (depth / drill) is NOT included here because it requires layer-stack walking; if you need it, fetch pcb_get_layer_stackup separately.

Returns: Dict with all keys above. All linear measurements use the stated unit suffix (_mm vs _mils).

pcb_lock_net_routingA

Lock or unlock track / arc / via primitives on a list of nets.

Standard workflow: before letting the autorouter touch the board, lock the power / ground / clock / oscillator nets so a partial re-route pass doesn't undo your careful hand-routing on them. Set lock=False afterwards to free everything up.

Locked primitives have Moveable=False; the autorouter and the interactive editor respect that flag when DXP Preferences → PCB Editor → General → "Protect Locked Objects" is enabled.

The net-list-driven MCP equivalent of a cursor-driven lock.

Args: nets: Net names to lock / unlock (e.g. ["VCC", "GND", "CLK_24M"]). Case-sensitive. lock: True to lock (sets Moveable=False), False to unlock. lock_components: Also lock any component with at least one pad on a matched net. Useful to freeze the connectors / regulators that anchor a hand-routed power section. Default False.

Returns: Dict with: - locked: True / False mirror of the input - matched_primitives: track + arc + via objects whose net matched - updated_primitives: how many actually changed state - matched_components: components touched (only filled when lock_components=True) - updated_components: components whose Moveable flipped

pcb_set_text_visibilityA

Bulk-toggle component designator and / or comment visibility.

For the common workflows:

  • Hide all designators before generating an assembly PDF.

  • Show comments during a value-review pass.

  • Hide just the noisy 0402 designators while keeping IC labels.

Args: designators: If True/False, set Component.NameOn for matched components. None leaves designator visibility alone. comments: If True/False, set Component.CommentOn. None leaves comment visibility alone. filter: Restrict to these designator names (e.g. ["U1", "U2", "C5"]). None applies to every component. Match is case-sensitive whole-string.

At least one of designators / comments must be supplied.

Returns: Dict with: - matched: components inspected (after filter) - updated_names: components whose NameOn flipped - updated_comments: components whose CommentOn flipped

pcb_set_text_styleA

Set the height and stroke width of component designators or comments.

Silkscreen text size is usually a fabrication requirement (for example 0.8 mm high with a 0.1 mm stroke: height_mils=31.5, stroke_mils=3.94). Altium's stroke text has a height and a stroke width and no separate character width: the characters scale with the height.

For free texts use obj_modify on eTextObject with Height and StrokeWidth (filter IsDesignator=false).

Args: height_mils: Text height in mils. None leaves it. stroke_mils: Stroke width in mils. None leaves it. designators: Only these components (e.g. ["U1", "R5"]). None applies to every component. which: "designator" (default), "comment" or "both".

At least one of height_mils / stroke_mils must be given.

Returns: Dict with matched components, changed and failed texts, not_found designators, and texts: each one's height and stroke as read back after the write. success is false when any text did not take or a designator was not found.

pcb_check_placement_collisionA

Dry-run check whether moving a component to (x, y[, rotation]) would overlap any other placed component on the same side.

DOES NOT actually move the component. Computes the predicted axis-aligned bounding box at the proposed pose, then AABB-tests against every other component's current bounding rect. Use this BEFORE every pcb_move_components call when placing parts on a board that already has placed parts.

Args: designator: Component to test (must exist on the board). x: Proposed X position in mils (component reference point). y: Proposed Y position in mils. rotation: Proposed rotation in degrees. When None the target's current rotation is used. Quarter-turn deltas (~+/-90 from current) swap the AABB dimensions; other angles use the current-orientation bbox as an approximation. margin_mils: Extra clearance to require around the target. Default 0 (touching bounding boxes count as collision).

Returns: Dict with: - designator: target. - proposed: {x, y, rotation, bbox:{x1,y1,x2,y2}, margin_mils}. - clear: True if no collisions, False otherwise. - colliding_count: number of collisions. - colliding: list of {designator, bbox:{...}} per collider.

Caveats: - AABB only; rotated non-square footprints will report an inflated bbox. - Same-side check is automatic; the target's bbox is compared only against components on the same layer (Top vs Bottom). - Does not detect courtyard violations or pad-to-pad clearances, only solid-box overlap. Use DRC for actual clearance rules.

pcb_get_trace_lengthsA

Get total routed track length per net on the active PCB.

Sums all track segment lengths for each net. Useful for length matching analysis and checking differential pair balance.

Args: net: Optional net name filter. If provided, only returns the length for that specific net. Empty = all nets.

Returns: Dictionary with "trace_lengths" array (each with net name and length_mils) and "net_count"

pcb_get_layer_stackupB

Get the full PCB layer stackup information.

Returns copper layers with thickness, dielectric type/height/constant, and board name.

Returns: Dictionary with "layers" array (each with name, order, copper_thickness_mils, dielectric_type, dielectric_height_mils, dielectric_constant), "layer_count", and "board_name"

pcb_export_stackup_csvA

Write the layer stackup to a conventional fab CSV report.

Fetches the structured stackup (pcb_get_layer_stackup) and writes it as the board-house stackup table: one row per physical layer, copper and dielectric interleaved top-to-bottom, thickness in mil and mm, dielectric constant where it applies. Columns: index, layer, type, material, thickness_mil, thickness_mm, dielectric_constant.

Args: output_path: Destination .csv file. Defaults to workspace/<board>_stackup.csv.

Returns: {"output_path", "rows", "layer_count", "total_thickness_mils"} or an error dict if the board / stackup could not be read.

pcb_add_layerA

Insert a copper layer into the PCB layer stack.

Calls IPCB_LayerStack.InsertLayer with the requested TLayer enum. Valid names include MidLayer1..MidLayer30 (signal layers) and InternalPlane1..InternalPlane16 (power / ground planes). Top / Bottom are always present, they cannot be added.

Args: layer: Layer name, e.g. "MidLayer1", "InternalPlane1"

Returns: Dictionary confirming the layer was inserted.

pcb_remove_layerA

Remove a copper layer from the PCB layer stack.

Calls IPCB_LayerStack.RemoveFromStack on the requested layer. Does nothing if the layer is not currently in the stack.

Args: layer: Layer name, e.g. "MidLayer1", "InternalPlane2"

Returns: Dictionary confirming the layer was removed.

pcb_modify_layerA

Tune properties on an existing copper layer.

Every optional parameter is applied only if provided (non-empty string / non-zero number). Maps to: name IPCB_LayerObject.Name copper_thickness_mils IPCB_LayerObject.CopperThickness dielectric_type Dielectric.DielectricType (one of "none", "core", "prepreg", "surface") dielectric_height_mils Dielectric.DielectricHeight dielectric_constant Dielectric.DielectricConstant dielectric_material Dielectric.DielectricMaterial

Args: layer: Target layer name, e.g. "MidLayer1" name: New layer name (optional) copper_thickness_mils: New copper thickness in mils (optional) dielectric_type: "none" | "core" | "prepreg" | "surface" (optional) dielectric_height_mils: New dielectric height in mils (optional) dielectric_constant: New Dk value (optional) dielectric_material: New dielectric material string (optional)

Returns: Dictionary confirming the changes.

pcb_set_plane_netA

Tie an internal plane layer to a net.

An Altium internal plane is a NEGATIVE layer: the copper is everywhere except where the plane is cleared, and the net lives on the LAYER, not on a poured object. Pouring a polygon on a plane layer is a different thing and does not connect the plane, so this is the tool for "make Internal Plane 1 the PGND plane" -- pcb_place_polygon_rect is not.

The net must already exist on the board (see pcb_get_nets). The layer must be an internal plane; a signal layer is refused with NOT_A_PLANE. Layer names are resolved against the real stack, so both "Internal Plane 1" (as pcb_get_layer_stackup prints it) and "InternalPlane1" work, and a name this board does not have is refused with UNKNOWN_LAYER rather than silently retargeting another layer.

The net is written to the board's InternalPlaneNetName for that layer and READ BACK before reporting; if that does not take, the layer object's own net is tried and read back too. When neither reads back the net the call answers success: false with applied: false and a note, and the board is NOT marked changed.

Args: layer: Internal plane layer, e.g. "InternalPlane1" or the name the stackup reports for it net: Existing net name to tie the plane to, e.g. "PGND"

Returns: Dictionary with "success", "layer" (the RESOLVED layer), "layer_name", "net", "net_readback", "applied", "route" (which write took) and "note".

pcb_get_board_outlineA

Get the board outline vertices and bounding rectangle.

Returns outline geometry as a list of vertices (line segments and arcs) plus the bounding rectangle dimensions.

Returns: Dictionary with "point_count", "vertices" array (each with index, kind, x, y, and optionally cx, cy, angle1, angle2 for arcs), and "bounding_rect" (left, bottom, right, top in mils)

pcb_get_selected_objectsA

Get properties of currently selected objects on the active PCB.

Args: properties: Comma-separated property names to return. Available: ObjectId, X, Y, X1, Y1, X2, Y2, Layer, Net, Width, Name, Rotation, HoleSize, TopXSize, TopYSize, Size, Pattern, SourceDesignator, Text, Descriptor, Selected

Returns: Dictionary with "objects" array and "count"

pcb_set_layer_visibilityB

Show or hide a specific PCB layer.

Args: layer: Layer name string, e.g.: Copper: "TopLayer", "BottomLayer", "MidLayer1"-"MidLayer30" Overlay: "TopOverlay", "BottomOverlay" Mask: "TopPaste", "BottomPaste", "TopSolder", "BottomSolder" Plane: "InternalPlane1"-"InternalPlane16" Other: "DrillGuide", "DrillDrawing", "MultiLayer", "KeepOutLayer", "Mechanical1"-"Mechanical16" visible: True to show, False to hide

Returns: Dictionary with layer name and visibility state

pcb_repour_polygonsA

Repour every poured polygon on the open PCB, in pour order.

Each polygon is rebuilt through the API, so a changed clearance or board-edge rule takes effect. Shelved polygons stay shelved. repoured is true only when at least one polygon was rebuilt and none failed; each item reports its copper pieces and poured area before and after, so a repour that changed nothing shows.

Returns: repoured, counts of polygons, rebuilt, shelved and failed, and per-polygon items

pcb_set_board_shapeA

Define the physical PCB board outline as a rectangle.

Overwrites the current board shape. Use this right after creating a new PCB document to establish the board size before placing parts. Coordinates are in mils; (x1,y1) and (x2,y2) are opposite corners in any order.

Args: x1: First corner X in mils y1: First corner Y in mils x2: Opposite corner X in mils y2: Opposite corner Y in mils

Returns: Dictionary confirming the new outline rectangle

pcb_place_polygon_rectA

Drop a copper polygon pour on a rectangular area.

Useful for placing a ground plane or power plane: pass the board's corners, the layer, and the net name (typically "GND"). Set pour_over=False to force the pour around same-net tracks/pads instead of covering them.

Args: x1: First corner X in mils y1: First corner Y in mils x2: Opposite corner X in mils y2: Opposite corner Y in mils net: Net name to assign (empty = no-net fill, unusual) layer: Copper layer (default "TopLayer") pour_over: Pour over same-net objects (default True)

Returns: Dictionary confirming the polygon placement

pcb_place_via_arrayA

Stitch vias in a regular grid across a rectangle.

Typical use: GND stitching between top and bottom layer copper pours. Places vias at every (pitch × pitch) grid intersection inside the rectangle.

Args: x1: First corner X in mils y1: First corner Y in mils x2: Opposite corner X in mils y2: Opposite corner Y in mils pitch: Grid spacing in mils (default 50) net: Net to assign (typically "GND"; empty = no net) size: Via pad diameter in mils (default 30) hole_size: Drill hole diameter in mils (default 12) low_layer: Start layer (default "TopLayer") high_layer: End layer (default "BottomLayer")

Returns: Dictionary with count of vias placed and rectangle/pitch echo

pcb_place_viaA

Place a via at specific coordinates on the active PCB.

Sizes take fractions of a mil, or millimetres through size_mm and hole_size_mm, so a 1.2/0.6 mm via is exactly that and matches a metric Routing Via rule. Whole mils made it 1.194/0.610 mm. A hole as wide as the pad is refused.

Args: x: Via X position in mils y: Via Y position in mils net: Net name to assign (optional, empty = no net) size: Via pad diameter in mils (default 50) hole_size: Drill hole diameter in mils (default 28) low_layer: Start layer (default "TopLayer") high_layer: End layer (default "BottomLayer") size_mm: pad diameter in mm, used instead of size hole_size_mm: hole diameter in mm, used instead of hole_size

Returns: Dictionary with placed via position and size (mils)

pcb_place_tracksA

Place many track segments on the active PCB in ONE IPC round-trip.

PREFER THIS over placing one segment at a time whenever you have more than one segment to place. The whole batch is wrapped in a single PreProcess/PostProcess and a single save, so 50 tracks take roughly the same wall time as 1. Typical uses: routing a full net, laying down a whole stitch pattern, replicating a motif, drawing a keepout rectangle.

Args: tracks: List of track dicts. Each dict supports: x1, y1, x2, y2 (required, mils; decimals are kept) width (default 10, mils; decimals are kept, so a 0.1 mm rule stays 3.937), layer (default "TopLayer"), net_name (optional, empty = no net)

Example:
    [
      {"x1": 5010, "y1": 4785, "x2": 5070, "y2": 4785,
       "width": 10, "net_name": "NetC8_2"},
      {"x1": 5070, "y1": 4785, "x2": 5070, "y2": 4862,
       "width": 10, "net_name": "NetC8_2"},
    ]

Returns: Dictionary with "placed" and "failed" counts

pcb_place_viasA

Place many vias on the active PCB in ONE IPC round-trip.

PREFER THIS over pcb_place_via whenever there is more than one via: the batch is one PreProcess/PostProcess and one broadcast. Coordinates and sizes keep their decimals, so a via laid by a router on a sub-mil grid lands where the router put it.

Args: vias: List of via dicts. Each dict supports: x, y (required, mils), size (default 50), hole_size (default 28), low_layer (default "TopLayer"), high_layer (default "BottomLayer"), net (optional, empty = no net)

Returns: Dictionary with "placed" and "failed" counts and "unknown_layers" naming any layer pair that did not resolve.

pcb_place_arcA

Place an arc on the active PCB.

Creates a circular arc segment defined by center, radius, and angular range.

Args: x_center: Arc center X position in mils y_center: Arc center Y position in mils radius: Arc radius in mils start_angle: Start angle in degrees (default 0) end_angle: End angle in degrees (default 360 = full circle) width: Arc line width in mils (default 10) layer: PCB layer name (default "TopLayer")

Returns: Dictionary with placed arc geometry and layer

pcb_place_textA

Place a text string on the active PCB.

Args: text: Text content to place x: Text X position in mils y: Text Y position in mils layer: PCB layer name (default "TopOverlay"). Common choices: "TopOverlay", "BottomOverlay", "TopLayer", "BottomLayer", "Mechanical1"-"Mechanical16" height: Text height in mils (default 60), decimals allowed rotation: Rotation angle in degrees (default 0) stroke: Stroke width in mils; 0 keeps Altium's default. To resize existing designators use pcb_set_text_style.

Returns: Dictionary with placed text properties, height and stroke as read back from the placed text

pcb_place_fillA

Place a rectangular copper fill on the active PCB.

Creates a solid copper rectangle. Useful for thermal pads, ground planes, and copper pours in specific areas.

Args: x1: First corner X in mils y1: First corner Y in mils x2: Second corner X in mils y2: Second corner Y in mils layer: PCB layer name (default "TopLayer") net_name: Net name to assign (optional, empty = no net)

Returns: Dictionary with placed fill coordinates and layer

pcb_start_polygon_placementA

Start INTERACTIVE polygon pour placement on the active PCB.

This is an interactive command, it launches Altium's polygon placement mode. The user must then draw the polygon boundary interactively in Altium Designer (clicks define vertices, right-click or Escape completes). It does NOT create a polygon programmatically from coordinates.

Args: layer: Target copper layer (default "TopLayer") net_name: Net to assign to the polygon pour (optional)

Returns: Dictionary confirming polygon placement mode was initiated

pcb_create_design_ruleA

Create a new design rule on the active PCB.

Args: name: Rule name (e.g., "Min Clearance 6mil"). rule_type: Type of rule to create. Options: "clearance" - Electrical clearance (value = gap in mils). "width" - Track width constraint (value = min width in mils; max_value / favored_value optional). "via_size" - Hole size constraint (value = min hole in mils; max_value optional). "differential_pairs" - Differential-pair routing rule (value = min gap, max_value = max gap, favored_value = preferred gap, max_uncoupled_length = max uncoupled length in mils). "solder_mask_expansion" - The mask opening at each pad and via site (value = radial expansion in mils, SIGNED). A negative value contracts the opening, which is how a via ends up covered. Scope it with "IsVia" to tent vias and leave pads alone. "paste_mask_expansion" - Same shape, stencil side (value = radial expansion in mils, signed). "vias_under_smd" - Whether the autorouter may put a via inside an SMD pad. Boolean: set allowed, not value. This is the DRC that catches via-in-pad, which needs filling and capping at fabrication. value: Rule's primary value in mils. For width / via_size / differential_pairs this is the MIN side. Default 10. max_value: For width / via_size / differential_pairs. When supplied, sets the MAX side independently of value. When None, falls back to 5x value (legacy default). favored_value: For width / differential_pairs. Sets the preferred value. Defaults to value when None. max_uncoupled_length: For differential_pairs only. MaxUncoupledLength in mils (single scalar, not per-layer). Defaults to 1000 mils when None. scope: Optional query expression for Scope1. Pick the predicate that matches the rule kind: - net-based rules (clearance, width, via_size): "InNet('GND')", "InNetClass('Power')", "All". - differential_pairs: "InDifferentialPair('USB')", "InDifferentialPairClass('HighSpeed')", "IsDifferentialPair" (matches any diff pair), or "All". Mixing predicates across kinds (e.g., InNet on a differential_pairs rule) creates a rule that never matches. Bracket a comparison joined by And/Or/Xor/Not: "IsPad And (HoleSize > 3)", or use &&/||. Altium binds And tighter than >, and an unbracketed "IsPad And HoleSize > 3" is refused before anything is sent, with the bracketed form in suggested_scope. net_scope: Which nets the rule applies between. Options: "different_nets" (default) for Clearance rules; "any_net" for all-pairs; "same_net" for same-net only. Has no effect on differential_pairs, and none on the mask or vias_under_smd kinds, which are not net-pair rules. allowed: For vias_under_smd only. False forbids a via inside an SMD pad. Omitted means True, which is Altium's own default and creates a rule that changes nothing, rather than one that silently bans via-in-pad board-wide.

Returns: Dictionary with created rule details.

Note: Creating ComponentClearance, HoleToHoleClearance, BoardOutlineClearance, and other newer rule kinds is not supported on this Altium build because the relevant symbolic constants (eRule_ComponentClearance, ...) are not exposed in DelphiScript. Existing rules of those kinds can be UPDATED via pcb_set_rule_properties (gap_mils field), which dispatches through Rule.RuleKind without needing the symbol.

pcb_delete_design_ruleC

Delete a design rule by name from the active PCB.

Args: name: Exact name of the design rule to delete

Returns: Dictionary confirming deletion

pcb_get_component_padsA

Get all pads of a specific PCB component.

Returns detailed pad information including pin name, position, net assignment, size, and hole information.

DATASHEET DISCIPLINE: Pad name -> pin function mapping comes from the footprint, which can be wrong (especially the thermal pad on QFN/DFN, which is rarely numbered consistently across vendors). Before stating which pin a pad corresponds to, cross-check the manufacturer datasheet's mechanical / recommended-land-pattern section. The response carries _datasheet_guidance + _datasheet_parts.

Args: designator: Component reference designator (e.g., "U1", "J3")

Returns: Dictionary with "designator", "pads" array (each with name, x, y, net, layer, hole_size, top_x_size, top_y_size, rotation), "pad_count", plus _datasheet_guidance + _datasheet_parts.

pcb_flip_componentB

Flip a component to the other side of the board (top to bottom or bottom to top).

Args: designator: Component reference designator (e.g., "U1", "R5")

Returns: Dictionary with designator, old_layer, and new_layer

pcb_align_componentsB

Align multiple PCB components along a common edge or center.

Args: designators: Comma-separated component reference designators (e.g., "R1,R2,R3,R4") alignment: Alignment mode. Options: "left" - Align to leftmost X "right" - Align to rightmost X "top" - Align to topmost Y "bottom" - Align to bottommost Y "center_x" - Center horizontally "center_y" - Center vertically

Returns: Dictionary with alignment result and component count

pcb_snap_to_gridA

Snap a component to the nearest grid point.

Rounds the component's X and Y position to the nearest multiple of the specified grid size.

Args: designator: Component reference designator (e.g., "U1", "R5") grid_size: Grid spacing in mils (default 50)

Returns: Dictionary with designator, old and new positions, and grid_size

pcb_get_diff_pair_rulesA

Get all differential pair routing rules from PCB design rules.

Returns design rules of kind eRule_DifferentialPairsRouting, these are routing rules, NOT IPCB_DifferentialPair pair objects on the board.

Returns: Dictionary with "diff_pair_rules" array (each with name, enabled, scope_1, scope_2, comment, descriptor) and "count"

pcb_get_viasA

Get all vias on the active PCB board.

Returns via position, pad size, hole size, net assignment, and start/end layer for every via on the board.

Returns: Dictionary with "vias" array (each with x, y, size, hole_size, net, low_layer, high_layer) and "count"

pcb_calc_polygon_areaA

Report the area of each copper polygon on the active board.

Returns each polygon's overall boundary area in square mils and square millimetres, optionally filtered by net and/or layer. Useful for estimating copper coverage / plane area.

Args: net: Only polygons on this net (optional). layer: Only polygons on this layer name (optional).

Returns: Dict with polygons (each: name, net, layer, area_sq_mils, area_sq_mm) and count.

pcb_set_via_soldermask_reliefA

Open the solder mask round each via's barrel, measured from the hole edge, on both sides.

Every via, or those on net, gets its mask expansion set as a manual value with "from hole edge" on, so the opening is the hole plus expansion_mils all round. Both sides are written: a via stores a bottom value even while its sides are linked, and that value takes effect if they are ever unlinked. Each via is read back, and vias_verified counts those holding the value.

WRITTEN THROUGH THE VIA'S PAD CACHE. Assigning the expansion straight onto a via crashes Altium's scripting engine (an access violation in ScriptingSystem.DLL, measured on AD 26.10); the same value written into the cache a via shares with pads takes and reads back, so the handler only ever uses that route.

A Solder Mask Expansion rule scoped IsVia is the alternative that also covers vias placed later: pcb_create_design_rule(rule_type="solder_mask_expansion", scope="IsVia", value=<mils>).

Args: expansion_mils: Mask expansion from the hole edge, in mils. Default 4. net: Only vias on this net. Empty means every via.

Returns: vias_matched, vias_verified, expansion_mils, net, from_hole_edge.

pcb_get_mech_layer_namesA

Every enabled mechanical layer on the board, and what is on it.

WHAT IS ON THE LAYER IS THE PART YOU CANNOT GET ELSEWHERE. A PcbLib header carries USEDBYPRIMS; a PcbDoc does not, so on a board there is no way to ask which mechanical layers the geometry occupies. This counts, because moving kinds around on a board you cannot inspect is how an assembly drawing gets orphaned. primitive_count of 0 is a layer that is genuinely spare; -1 means the count was not attempted or failed.

ENABLED, NOT DISPLAYED. This used to list only layers the view happened to be showing, so a layer carrying the whole fab drawing was missing from the answer whenever it was toggled off. Display is a view setting and is reported separately as displayed.

Each entry carries its kind, which is what the layer is FOR rather than what it is called. Read it before setting one: a kind belongs to a single layer, so assigning it moves it, and a PAIRED kind is held by the layer pair. kind_id of -1 means this Altium build has no mechanical layer kinds.

Args: count_primitives: Count what sits on each layer. The board is walked once per enabled layer, so pass False when only the names are wanted.

Returns: Dict with mechanical_layers (each: layer, number, name, enabled, displayed, kind, kind_id, primitive_count), count, occupied_count, counted_primitives and scanned_to, the layer number the scan stopped at.

pcb_set_mech_layersA

Name, enable and kind the mechanical layers of the open BOARD.

The board counterpart of lib_set_mech_layers, which takes a library by path and refuses a PcbDoc. Without this a board could only have kinds set, one layer per call through pcb_set_mech_layer_kind, and could not be renamed at all above Mechanical16.

Every change is READ BACK. A layer whose name, enable or kind did not take is reported against that layer rather than folded into an overall pass.

PAIRED KINDS. Any kind ending in Top or Bottom is held by the layer PAIR rather than by either layer, under a shorter name with the side dropped, so "Courtyard Top" is stored as the pair kind "Courtyard". Give both sides in the same call and the two are joined and the pair given the kind. Single kinds such as "Fab Notes" need no partner.

READ THE BOARD FIRST. pcb_get_mech_layer_names reports primitive_count per layer, and a kind moved off a layer that is carrying geometry orphans that geometry from whatever resolves the layer by purpose.

Args: layers: One dict per layer. Keys: layer (required, e.g. "Mechanical13"), and any of name, enabled (bool), kind (e.g. "Courtyard Top"). A layer with none of the three is reported as nothing asked for. tidy_pairs: Remove layer pairs no kind justifies. Off by default, because it REMOVES pairs. timeout: Seconds.

Returns: Dict with document, layers (each: layer, changed, problem), changed, failed, kinds_displaced, pairs_removed, pairs_tidied and pairs_scanned_to.

pcb_get_layer_displayA

Visibility and colour for every layer on the active board.

Covers signal, plane, mechanical, mask, paste, silk, keepout and multilayer. Each entry gives the internal layer id, the user's name for it, whether it is visible, and its colour both as Altium's raw integer and as color_hex.

Read this before recolouring: Altium stores a colour as a Windows TColor, which is byte-reversed against the #RRGGBB people write, and getting that backwards produces a plausible wrong colour rather than an error.

Returns: Dict with layers, count and visible_count.

pcb_set_layer_colorA

Recolour one layer.

The colour is given as #RRGGBB. Altium holds it internally as a byte-reversed TColor, and that conversion happens on the Altium side so no caller has to know about it.

The write is READ BACK before reporting success, because a refused assignment raises nothing and would otherwise be reported as having worked.

Colour is a display setting rather than a property of the board, so it follows the installation rather than travelling with the file.

Args: layer: Layer name, e.g. "TopOverlay" or "Mechanical13". color: Colour as "#RRGGBB".

Returns: Dict with layer, color and color_hex.

pcb_set_mech_layer_kindA

Set what a mechanical layer is FOR, not what it is called.

Renaming a layer "Courtyard Top" does not make it one. The KIND is a separate property, and it is what every feature resolving a layer by purpose reads: courtyard checking, assembly drawings, 3D body placement and the IPC-4761 via treatments. A layer with no kind is skipped by all of them, so the outlines get drawn and nothing uses them.

A SINGLE kind such as "Fab Notes" belongs to ONE layer at a time. If another layer already holds it, that layer is cleared first and named in cleared_from, because leaving two layers claiming one purpose is a state the stack manager does not intend.

A PAIRED kind, meaning any name ending in Top or Bottom, works differently: it is held by the layer PAIR rather than by either layer, under a separate set of names with the side dropped. So "Courtyard Top" is stored as the pair kind "Courtyard" across the two layers, and partner_layer is required to say which layer carries the other side. Writing it without a partner is refused rather than silently doing nothing.

The write is READ BACK before reporting success. A refused assignment raises nothing here, so trusting it would report success for having changed nothing.

Args: layer: Mechanical layer, e.g. "Mechanical13". kind: A kind name such as "Courtyard Top", "Assembly Top", "3D Body Top", "Component Outline Top", "Fab Notes" or "Not Set". A bare number is also accepted, so a kind added by a later Altium release can be set without waiting for the name map to catch up. Read the current assignment with pcb_get_mech_layer_names. partner_layer: The mechanical layer carrying the other side, required when kind ends in Top or Bottom and ignored otherwise. The two layers are joined as a pair, which is what the kind is then written to.

Returns: Dict with layer, kind, kind_id, paired, pair_kind, partner_layer and cleared_from.

pcb_delete_objectA

Delete a PCB object closest to specific coordinates on a layer.

Finds the nearest matching object within 100 mils of the given coordinates and removes it from the board. Distance is to the object itself: a track by its segment less half its width, an arc by its curve, a via by its centre less its radius, anything else by its bounding rectangle (0 inside it). A point anywhere on a track finds that track.

Args: x: Target X position in mils y: Target Y position in mils layer: PCB layer name (default "TopLayer") object_type: Type of object to delete. Options: "track" - Track segment "via" - Via "fill" - Copper fill "text" - Text string "pad" - Free pad "arc" - Arc "polygon" - Copper polygon pour "region" - Region / split-plane "component" - Placed component (for bulk/filter-based deletes use obj_delete)

Returns: Dictionary with deleted status, object_type, distance_mils, the deleted object's net and bbox_mils, and others_as_close: how many other objects were exactly as near, so a pick among equals is visible.

pcb_get_pad_propertiesA

Get detailed pad information filtered by net or component.

Returns pad shape, size, hole, thermal relief, and solder/paste mask expansion details. Provide at least one filter (net or designator) to avoid returning all pads on the board.

Args: net: Filter by net name (e.g., "GND", "VCC"). Optional. designator: Filter by component designator (e.g., "U1"). Optional.

Returns: Dictionary with "pads" array (each with name, component, x, y, net, layer, shape, top_x_size, top_y_size, hole_size, rotation, is_smd, solder_mask_expansion, solder_mask_bottom_expansion, solder_mask_separate, paste_mask_expansion) and "count". solder_mask_expansion is the TOP side when solder_mask_separate is true and both sides when it is false; the bottom value is stored either way and takes effect once the sides are unlinked. Expansions read 0 when the pad follows the board's rule.

pcb_set_track_widthA

Modify track width for all tracks on a specific net.

Changes the width of every routed track segment assigned to the given net. Useful for adjusting power or signal trace widths.

Args: net_name: Name of the net whose tracks to modify (e.g., "VCC") width_mils: New track width in mils (e.g., 10, 20, 50)

Returns: Dictionary with net_name, width_mils, and tracks_modified count

pcb_get_unrouted_netsA

Get list of nets with unrouted connections (ratsnest lines).

Identifies nets that still have ratsnest lines, meaning they are not fully routed. Useful for checking routing completion status.

The count is of the connection lines stored on the board, and those can be out of date: boards whose nets were all joined have been reported with open connections. reanalyze has Altium re-derive the lines of every net that has one first, as it does after an edit. obj_query on eConnectionObject lists each line with its ends, layers, IsRedundant and Mode.

Args: reanalyze: re-derive the ratsnest before counting (default False).

Returns: Dictionary with "unrouted_nets" array (each with net name and unrouted_connections count), "net_count", "total_unrouted" and "reanalyzed"

pcb_get_polygonsA

Get all polygon pours on the active PCB with copper area.

Each polygon row carries:

  • index, name: positional + Altium-assigned name

  • net: assigned net (often GND / a power rail)

  • layer: TopLayer, InternalPlane1, etc.

  • hatch_style: Solid / 45Degree / Horizontal ...

  • pour_over: pour-policy flag

  • area_sqmils / area_mm2: the polygon OUTLINE's area. It does not change when the pour does, and it includes copper a clearance or the board edge has cut away.

  • copper_area_mm2: the copper actually poured, summed over the polygon's regions with the holes cleared around other nets taken off. Use this for current-capacity audits (multiply by copper thickness for cubic copper, then apply IPC-2152 / IPC-2221). A hatched pour is tracks, so it reports copper_pieces with a region area of 0.

  • copper_area_exact: false when a region's holes could not be read, in which case copper_area_mm2 is the regions' outer area and overstates the copper.

  • copper_pieces: how many pieces the pour is in; 0 on a poured polygon means nothing was poured.

  • poured: false for a shelved polygon.

  • bbox_mm2: bounding-rectangle area; ratio copper_area_mm2 / bbox_mm2 shows how much of the outline is real copper.

  • vertex_count: a smooth pour has 4-8 vertices; many more usually means hand-editing that may have introduced narrow necks.

Pair with pcb_calc_track_current_capacity for per-net current-carrying analysis.

Returns: Dict with {polygons: [...], count: N}.

pcb_modify_polygonA

Modify a polygon pour's properties and its pour options.

Changes net, layer, hatching style, or the three "remove" options from the Polygon Pour dialog. Use pcb_get_polygons first to find the polygon index.

NOTHING HERE REPOURS. The pour options and the hatch style decide what the NEXT pour produces, so the board does not change until pcb_repour_polygons runs. The reply carries repour_needed for that reason: a change that has been recorded and not yet poured is otherwise indistinguishable from one that did nothing.

Args: index: Polygon index (from pcb_get_polygons output) net: New net name to assign (optional, empty = no change). A name that matches no net on the board is reported under not_applied rather than ignored. layer: New layer name (optional, empty = no change) hatch_style: New hatch style (optional). Exactly four are accepted, and anything else is reported rather than silently dropped: "Solid" - solid copper fill "NoHatch" - outline only, no fill "45Degree" - 45-degree crosshatch "90Degree" - 90-degree crosshatch remove_dead: Remove dead copper, the islands with no connection to the polygon's net. None leaves it unchanged. remove_narrow_necks: Remove necks narrower than the polygon's threshold. None leaves it unchanged. remove_islands_by_area: Remove islands below the polygon's area threshold. None leaves it unchanged.

Returns: {"modified": bool, "success": bool, "index": N, "name": ..., "changed": [field names], "not_applied": [{item, reason}], "repour_needed": bool, "note": ...}.

``modified`` reports whether anything actually changed, not
whether the call ran. ``changed`` names each field that took.
pcb_place_3d_bodyA

Place a STEP model directly on the open PCB, as a free 3D body.

The equivalent of Altium's Place > 3D Body > Generic STEP Model: the model lands on the document itself, not inside a component.

USE THIS RATHER THAN lib_link_3d_model WHEN THE MODEL IS NOT A PART. That tool writes into a .PcbLib footprint, so putting a fixture, an enclosure or a device-under-test onto a board with it means inventing a library, authoring a footprint, placing it and then deleting all of that again. Reach for it only when the model genuinely belongs to a component in a library.

Rotation cannot be set from here. IPCB_ComponentBody exposes no Rotation, and the rotation that does exist lives on the model behind an undocumented four-argument call, so the reply reports rotation_applied: false rather than guessing. Rotate in the editor if the orientation is wrong.

Args: model_path: absolute path to the .step / .stp file. x: body origin X in mils. This is the model's own origin, so where it lands depends on how the STEP was authored; check it against a known feature such as a silkscreen outline rather than assuming it is centred. y: body origin Y in mils. layer: board layer to attach it to. Default TopLayer. standoff_height: height in mils of the body's underside above the board. 0 sits it on the surface.

Returns: {"success": true, "model_path": ..., "x": N, "y": N, "layer": ..., "standoff_height": N, "standoff_applied": bool, "rotation_applied": false, "note": ...}. The x and y are READ BACK from the placed body, not echoed, because the position is the one thing that cannot be checked without opening the 3D view.

pcb_get_room_rulesA

Get all room-like rules (confinement constraint design rules).

Returns design rules of kind eRule_ConfinementConstraint, these are NOT physical IPCB_Room objects on the board. The rule bounding rect is reported as x1/y1/x2/y2 in mils.

Returns: Dictionary with "room_rules" array (each with name, enabled, kind, scope_1, comment, x1, y1, x2, y2) and "count"

pcb_create_roomA

Create a room for component grouping on the active PCB.

Creates a confinement constraint rule that defines a rectangular region. Components can be assigned via scope expression.

Args: name: Room name (e.g., "Power Section", "USB Block") x1: First corner X in mils y1: First corner Y in mils x2: Second corner X in mils y2: Second corner Y in mils components: Comma-separated component designators to confine (e.g., "U1,U2,R1,R2"). Optional; empty = applies to all.

Returns: Dictionary with created status, name, coordinates, and scope

pcb_get_board_statisticsB

Get comprehensive statistics for the active PCB board.

Returns counts of all object types, total trace length, board dimensions, and layer count. Useful for design reviews and progress tracking.

Returns: Dictionary with track_count, via_count, pad_count, component_count, fill_count, text_count, polygon_count, unrouted_connections, total_trace_length_mils, board_width_mils, board_height_mils, board_area_sq_mils, layer_count, and board_name

pcb_export_coordinatesA

Export component placement coordinates formatted for pick-and-place (like pcb_get_components, but the pick-and-place shape: adds side, omits bbox detail).

Returns designator, footprint, comment, position (x, y), rotation, layer, and side (Top/Bottom) for every component. Useful for manufacturing pick-and-place machine programming.

Returns: Dictionary with "placements" array (each with designator, footprint, comment, x, y, rotation, layer, side) and "count"

pcb_create_diff_pairA

Create a differential pair object from two existing nets.

The two nets must already exist on the board (typically present after update_pcb / ECO). The diff-pair object lets Altium apply differential routing constraints and the interactive router to honour impedance / matched-length rules between the pair.

Args: positive_net: Positive-side net name (e.g. "USB_DP") negative_net: Negative-side net name (e.g. "USB_DM") name: Optional diff-pair name (defaults to "_")

Returns: Dictionary confirming creation with name, positive_net, negative_net

pcb_place_regionA

Place a solid copper region on a rectangular area.

Regions are solid primitives; unlike polygons, they don't participate in the connectivity engine unless you assign a net. Use for mechanical copper zones, thermal pads, or solder-mask openings. For a true ground plane with ratsnest tracking, prefer pcb_place_polygon_rect.

Args: x1, y1, x2, y2: Opposite corners in mils (any order) layer: Copper or mech layer (default "TopLayer") net: Optional net assignment

Returns: Dictionary confirming the region placement

pcb_place_dimensionA

Place a linear dimension between two points.

Horizontal dimension measures delta-X, vertical measures delta-Y. Orientation auto-detects from the larger axis delta if not given.

Args: x1, y1: First reference point in mils x2, y2: Second reference point in mils layer: Layer to draw the dimension on (default "TopOverlay") orientation: "horizontal" or "vertical" ("" = auto)

Returns: Dictionary confirming the dimension placement

pcb_place_padA

Place a standalone pad on the active PCB.

Not part of any component. Use for fiducials, test points, mounting holes. Set hole_size=0 for surface-mount pads, nonzero for through-hole.

Args: x, y: Position in mils name: Pad designator / label (optional) net: Net to connect to (optional) shape: "round" (default) / "rect" / "oct", or spelled out as the library tools take them ("rectangular", "octagonal"). Any other word is refused; it used to become a round pad. x_size, y_size: Pad dimensions (default 60 mil) hole_size: Drill diameter (0 = SMD) layer: Copper layer (default "TopLayer") units: "mil" (default) or "mm", for the position and every size, kept exact rather than rounded to whole mils.

Returns: Dictionary confirming pad placement

pcb_place_componentsA

Place one or many footprints from PcbLibs onto the board in ONE call.

The scriptable substitute for ECO / Design ▸ Update PCB Document: Altium's ECO always raises a modal, so for unattended population this drops footprints straight onto the board. It resolves the board once and places every footprint in a single Altium transaction (one IPC round-trip). Pass a single-element list to place just one.

Two modes per component: geometry only (omit unique_id / pad_nets) leaves pads unconnected (DRC flags them), good for artwork / panelization / fiducials / placement studies; synced (pass both) stamps the schematic UniqueId so a later ECO matches the part and creates+assigns each pad's net for real connectivity. Read unique_id via query_objects(eSchComponent, "Designator.Text,UniqueId") and pad_nets from the compiled netlist (proj_get_connectivity_many → pad number → net).

board_path: target a specific .PcbDoc when several are open (resolved once for the whole batch). Otherwise the focused board is used.

Args: placements: List of placement dicts, each with: footprint (required), library_path (required, .PcbLib), x, y (mils), designator, lib_reference, comment, rotation, layer, unique_id, and pad_nets ({pad_name: net_name}); omit unique_id/pad_nets for geometry-only, include them for a synced placement. board_path: Absolute .PcbDoc path to place onto (optional).

Returns: Dict with placed (count), failed (count), total.

pcb_place_angular_dimensionA

Place an angular dimension (angle between two reference directions).

Args: center_x, center_y: Vertex of the angle in mils x1, y1: First reference direction endpoint in mils x2, y2: Second reference direction endpoint in mils radius: Arc radius at which to draw the dimension in mils layer: Layer (default "TopOverlay")

Returns: Dictionary confirming the angular dimension

pcb_place_radial_dimensionB

Place a radial dimension around a center point with a given radius.

Args: center_x, center_y: Center point in mils radius: Radius to dimension in mils layer: Layer (default "TopOverlay")

Returns: Dictionary confirming the radial dimension

pcb_distribute_componentsA

Evenly space components along an axis.

Moves each named component so its X (or Y) coordinate lands at equally-spaced stops from start to end. Order follows the designators list, designators="R1,R2,R3" with start=0 end=200 places R1 at 0, R2 at 100, R3 at 200 on the chosen axis. Y (or X) is untouched.

Args: designators: Comma-separated list of component designators axis: "x" or "y" (default "x") start: First position in mils end: Last position in mils

Returns: Dictionary with distribution result

pcb_get_rule_propertiesA

Read properties of a named PCB design rule.

Returns metadata (name, rule_kind, enabled, priority) plus a descriptor string that contains the rule's constraint values in human-readable form, e.g.:

"Width Constraint (Min=0.102mm) (Max=5.08mm) (Preferred=0.127mm) (All)"
"Clearance Constraint (Gap=0.127mm) (All),(All)"
"Hole Size Constraint (Min=0.1mm) (Max=4mm) (All)"
"Routing Via (Templates Used To Check Via: v30h10m0mx0, ...) (All)"

WHY THE DESCRIPTOR AND NOT THE NUMBERS. Constraint values live on per-kind subtype interfaces (IPCB_ClearanceConstraint, IPCB_MaxMinWidthConstraint, ...). This handler holds a base IPCB_Rule, where those members do not exist, so reading them gives nothing and the descriptor is what is offered instead. Parse it client-side if you need numeric values.

THAT IS A LIMIT OF THIS READ, NOT OF THE API, and the difference matters because the sentence above has already been read as "constraints are unreachable" and stopped someone who then reported a rule as unwritable. DelphiScript DOES narrow an interface, but only at iterator return, so a typed local assigned straight from BoardIterator.FirstPCBObject reaches the constraint members. pcb_set_rule_properties writes them that way. To CHANGE a value, use that tool. obj_modify will not do it: it dispatches through the base reference and reports a match while changing nothing.

A clearance-family rule also returns gap_mm, read from the rule itself; for Board Outline Clearance the descriptor says Gap=0mil whatever the rule holds, so read gap_mm.

Args: name: Design rule name (e.g., "Clearance", "Width", "RoutingVias").

Returns: Dict with name, rule_kind, enabled, priority, descriptor, and gap_mm for a clearance-family rule.

pcb_set_rule_propertiesA

Update metadata AND constraint values of a named PCB design rule.

Metadata fields always apply. Constraint fields are dispatched by the rule's underlying RuleKind:

  • Clearance (kind 0), ComponentClearance (kind 24), HoleToHoleClearance (kind 52) and BoardOutlineClearance (kind 63): gap_mils, or gap_mm when the value is metric. An integer mil field cannot express 0.2mm; it lands on 8 mils, which is 0.2032mm. Pass one or the other.

  • Width (kind 2): min_width_mils / max_width_mils / favored_width_mils (applied to every layer).

  • HoleSize (kind 42): min_hole_size_mils / max_hole_size_mils.

  • Routing Via: min_via_size_mils / max_via_size_mils / preferred_via_size_mils and the three *_via_hole_mils. Checked as a set first (minimum <= preferred <= maximum, every diameter larger than its hole) and each read back; the reply carries via_sizes_mils and via_sizes_written. A rule in template mode keeps checking its templates, and via_note says so; this cannot switch a rule between modes.

THE GAP WRITE IS READ BACK, not assumed. A rule kind that does not really carry Gap can accept the assignment and keep its old value, so the reply carries gap_written, gap_after_mm and, when it did not take, a note saying the rule still holds the old number. Kind 63 in particular is included on the strength of its position in the TRuleKind order rather than a documented interface, so let gap_written decide whether it worked rather than the count.

Pass only the parameters you want to change; everything else stays untouched. Each VERIFIED field write increments properties_updated in the response, so a refused constraint no longer inflates it.

NOTE: Priority is NOT writable from this tool. IPCB_Rule.Priority is a read-only function in the PCB scripting API (Function Priority : TRulePrecedence), not a property; there is no SetState_Priority or SetPriority method either. Assigning to it crashes the DelphiScript engine. To reorder rule priorities, drag them in Altium's UI (PCB > Rules and Constraints Editor; rules earlier in a category have higher priority).

Args: name: Rule name to update. enabled: Whether DRC enforces the rule. scope1 / scope2: Rule scope query expressions. A comparison joined by And/Or/Xor/Not must be bracketed ("IsPad And (HoleSize > 3)") or joined with &&; an unbracketed one is refused before anything is sent. comment: Free-form comment. gap_mils: Clearance gap. Applies to Clearance, ComponentClearance, and HoleToHoleClearance rules. min_width_mils / max_width_mils / favored_width_mils: Width constraint values (applied to every layer). min_hole_size_mils / max_hole_size_mils: HoleSize constraint limits.

Returns: Dict with name, rule_kind, and properties_updated count.

pcb_place_embedded_boardA

Place an embedded-board array (panel) referencing a child PCB.

An embedded board is a grid of copies of a child .PcbDoc file, used for panelization (multi-up arrays, step-and-repeat).

Args: child_path: Full path to the child .PcbDoc file. x, y: Bottom-left corner of the array in mils. rows, cols: Grid size (default 1x1). row_spacing_mils, col_spacing_mils: Gap between array cells. mirror: Flip the child board over when True. layer: Placement layer for the embedded-board primitive.

Returns: Dict with success, child_path, rows, cols, x, y.

design_review_snapshotA

Fetch a comprehensive design-review snapshot in ONE tool call.

THIS -- the compiled netlist + part data it returns -- is the BASIS of a design review, not a render. Judge connectivity, part values, ratings, and pin functions from these sections (and the cited datasheets), never from sch_render_svg / pcb_render_svg / design_visual_review, which are geometry-only and exist for when an image is genuinely needed (layout/placement). A picture can show a wire that shares no net, or hide a net that is electrically correct -- so the review reads the data here.

PREFER THIS over running 8-12 individual review queries. A normal review (components, nets, rules, diff, messages, stats, unrouted, BOM) is one round-trip instead of one LLM turn per section. That's the single biggest time cost on a full review.

CRITICAL, datasheet discipline (enforced via _review_guidance): Before drawing ANY conclusion about a component's pin function, voltage rating, or electrical spec, fetch the actual manufacturer datasheet and verify. The schematic symbol, footprint, and parameter fields are NOT ground truth - they are often wrong or outdated. Every proposed fix must cite the datasheet section or page you relied on.

The response's _unique_parts field is the checklist of components whose datasheets you must have read before reviewing. Do not skip this step.

CRITICAL, review-quality discipline (enforced via _review_guidance): A finding must ADD analysis the raw sections do not. Run ERC and DRC, but do NOT relay their raw output as findings - investigate each violation after the datasheet review, drop the false positives, and surface the real ones as specific, traced findings (a genuine ERC/DRC violation can be a top finding - never dropped). Do NOT emit "verify X" / "confirm X" as a finding - that hands the analysis back to the user. Every finding names the exact parts/pins/nets, is backed by a netlist fact or a cited datasheet, states a concrete consequence and fix, and is something you would raise in a real design review. The full rules are in _review_guidance["review_quality_rules"].

Args: sections: Which snapshot sections to include. Defaults to the standard review set (project_info, design_stats, components, nets, design_rules, unrouted, diff, messages, board_stats, fab_stats). Available extras: "bom", "drc", "erc", "unconnected_pins", "project_options". include_bom: Convenience - adds "bom" to sections if True (default). BOM is the best source of manufacturer part numbers for datasheet lookup. run_drc: If True, runs DRC (slow, 30-90 s) and includes results. Off by default - assume the user already ran it, or run it separately when asked. run_erc: If True, runs ERC and includes results. Off by default. force_recompile: SaveAll + invalidate SmartCompile cache + recompile before gathering any sections. Use this when the user has been editing schematics in the Altium UI and you need a guaranteed-fresh netlist. Costs one extra ~5-10 s compile up-front.

Returns: Dict with one key per requested section, plus: - _review_guidance: datasheet + review-quality rules Claude must follow when turning sections into findings - _unique_parts: list of {manufacturer, part_number, designators} to fetch datasheets for - _sections_failed: sections that errored (partial results are still usable)

design_lint_reportA

Run all design-lint checks and return a consolidated violation report.

Sequences the individual audit.* checks the way a pre-release review would: compile-gate first, then schematic-side rules, then PCB-side rules, then optional ERC/DRC. Each section's raw result is preserved so an agent can drill in; a top-level summary gives counts per check.

Returns structured JSON instead of dumping to the Messages panel (Altium's panel is fine for human review but the agent needs a machine-readable shape).

Checks executed:

  • component_param_visibility -- per-class parameter visibility (cap voltage, IC MPN, etc).

  • power_port_orientation -- ground down / rails up.

  • find_floating_ports -- SCH ports not connected to a pin or sheet entry.

  • tented_via_ratio -- PCB tented vs untented via surfaces.

  • find_bad_connections -- PCB tracks / arcs with dangling endpoints within bad_connection_tolerance_mils.

  • find_signal_vias_without_return -- PCB signal vias with no nearby ground/power via for return-current.

  • drc (only if run_drc=True) -- Altium's Design Rule Check, full project.

Args: run_drc: If True, also runs Altium's DRC and includes its violations. Off by default because DRC mutates the .DRC report file and can take ~5-10s on a busy board. bad_connection_tolerance_mils: Coord tolerance for find_bad_connections. Default 1.0; raise to surface only gross misalignments.

Returns: Dict with: - summary: per-check {checked, violations} (best effort -- missing/failed checks listed under _failed) - sections: per-check raw result objects under their section name - totals: overall violations count across all sections (does NOT include DRC raw count -- DRC items are listed separately because severity / fix-cost differs)

design_datasheet_checklistA

Return the datasheet-first discipline checklist for design review.

Use this when you need the rules without pulling the full snapshot. The rules also ship inside the _review_guidance block of design_review_snapshot - no need to call this separately if you already ran a snapshot.

Returns: Dict with datasheet_rules (list of rules) and a short action_required summary.

sim_get_readinessA

Audit every component on the active schematic for SPICE readiness.

Use this as step 1 of any simulation workflow. The response classifies each component into one of three buckets:

  • ready , SpicePrefix already set; nothing to do.

  • needs_primitive, a passive (R/L/C) or source (V/I/D/Q) that just needs SpicePrefix + Value. Pass them all to sim_attach_primitives in one call.

  • needs_file , an IC or active part that requires a manufacturer-supplied .mdl / .ckt / .lib file. Call sim_attach_model after fetching the file from the vendor.

CRITICAL, vendor SPICE models only: Never fabricate a SPICE model from datasheet reasoning. The parameters that matter for convergence, bias point, and small-signal behavior are not in any datasheet summary; a plausible-looking hand-rolled model produces confidently-wrong simulation results. The response's _spice_guidance block makes this rule explicit and gives you per-part search queries.

Returns: Dict with: - ready, ready_count - needs_primitive, needs_primitive_count (each entry has designator, comment, suggested_prefix, suggested_value) - needs_file, needs_file_count (each entry has designator, comment, lib_ref, manufacturer, manufacturer_part) - _spice_guidance (rules + structured search_hints + primitive_hints)

sim_attach_modelA

Attach an external SPICE model file (.mdl / .ckt / .lib) to a component.

Use this after fetching a vendor SPICE model file from the manufacturer and saving it locally. The model_name parameter must be the exact .SUBCKT <name> declaration from inside the file, not the file name.

Args: designator: Component reference (e.g. "U1"). file_path: Full path to the locally-saved .mdl / .ckt / .lib. model_name: Subcircuit / model name declared inside the file. Open the file and look for .SUBCKT <name> or .MODEL <name>. primitive: SPICE prefix letter. Default "X" (subcircuit). Use "D"/"Q"/"M" when the file declares a .MODEL of that kind instead of a subcircuit.

Returns: Dict with success, designator, file_path, model_name.

sim_runA

Run Altium's mixed-signal simulation of the focused project.

Runs the analyses set up for the project: with sim_setup_analysis, or in Altium's Simulation Dashboard. A run started from a script performs an operating point, DC sweep, transient and AC together (MEASURED: the dashboard's on/off switches are not honoured).

It succeeds only when the run produced its results document (a .sdf, opened in its own tab), and fails with SIM_NOT_RUN when nothing appeared. The results are saved and read back: the reply carries each analysis's waves with min, max, first and last, and sim_get_results gives the traces.

Typical workflow: 1. Activate one of the project's schematic sheets. 2. Use sim_get_readiness + sim_attach_* to make sure every component has a SPICE model. 3. Place probes on measurement nodes with sch_place_probe. 4. Set the analyses with sim_setup_analysis. 5. Call sim_run, then sim_get_results for the traces.

Args: analysis: Free-form label echoed back in the response. Does not drive Altium, the project's setup does.

Returns: Dict with success, ran, results_document (the .sdf path), results_saved, results (per analysis, per wave: unit, points, min, max, first, last; magnitude for an AC wave), analysis and note.

sim_setup_analysisA

Set the analyses sim_run will use, in the project's .simcfg.

Values are SPICE values: a number in SI units, or a string with a SPICE suffix ("5u", "10n", "1k", "100meg").

Args: transient: {"stop", "step", "start" (default 0), "max_step" (default step)}. ac: {"start", "stop", "points", "sweep": "dec" | "oct" | "lin"}. Points are per decade or octave, or in total for lin. dc: {"source" (a source designator such as "V1", or "Temp"), "start", "stop", "step"}. outputs: expressions to plot in each analysis set here, e.g. ["v(out)", "i(v1)"]. project_path: the project; empty uses the focused one.

WHAT A RUN DOES, MEASURED: a run started from a script performs an operating point, a DC sweep, a transient and an AC analysis together, whatever is switched off in the dashboard. The values set here are what those analyses use. An analysis you do not need still runs on its last settings; sim_get_results(analysis=...) returns just the one you want.

Altium's own settings are saved first, so changes made in the Simulation Dashboard are kept, then the file is changed and reloaded into Altium. A project without a .simcfg gets one.

Returns: Dict with simcfg (the file), set (the keys written), reloaded, and created when the file is new.

sim_get_resultsA

Read a simulation's results: every wave, with sampled points.

Reads the .sdf that sim_run saved (the last one by default). Each wave comes with its unit, point count, min/max/first/last over every point, and up to max_points samples evenly spaced from first to last. An AC wave reports magnitude and phase in degrees against frequency. The sweep variable (time, frequency, the swept source) is marked independent.

Args: results_document: path of a .sdf; empty uses the last sim_run. analysis: one analysis by name ("Transient Analysis", "AC Analysis", "Operating Point", "DC Sweep", ...); empty returns all. waves: wave names to keep, e.g. ["v(out)", "i(v1)"]; empty keeps all. max_points: samples per wave; 0 returns statistics only. all_runs: a document run more than once holds every run; by default only the latest run of each analysis is returned.

Returns: Dict with results_document and analyses: a list of {analysis, waves: [...]}, or an error naming what is missing.

design_get_disciplineA

Read the design discipline + DesignPlan JSON schema.

ALWAYS call this first when starting a design task. The result contains the hard rules the planner must follow (net-label-driven wiring, datasheet-first part choice, NDA isolation, etc.) plus the JSON schema that design_execute_plan enforces on input.

Returns: A dict with discipline (markdown text) and schema (DesignPlan JSON schema as a dict).

design_session_startA

Open a durable design-session journal for an autonomous run.

The journal is append-only state that survives context compaction, client restarts, and model switches, so any later client can resume the run from recorded fact instead of chat history. Call this once at the start of a spec-to-board task, then log stage results as you go and read design_session_resume to know what's next.

Args: requirement: the design requirement text this run implements. session_id: optional explicit id; defaults to a timestamp.

Returns: {"session_id": ..., "state": {...}}.

design_session_logA

Append one event to a design-session journal.

event is one of: stage_enter (needs stage), stage_result (stage + status = ok|blocked|failed), plan_revision (revision), artifact (path, kind), blocked (question), resolved (text = answer), note (text). Returns the updated derived state.

data (a dict, or JSON text) goes with a stage_result or a note. On a stage_result it is the evidence for the exit gate: for placement, routing, pours_tuning and verification, pass the pcb_layout_audit result (and the pcb_calc_length_match or proj_run_erc reply under its own tool name) and the reply carries gate, the verdict on those numbers. A stage logged ok whose numbers fail its gate is sent back by design_next_action. topic files a note under a section of design_session_report: placement, stackup, critical_routes, power, planes (fan-out and via-in-pad too), silkscreen, verification, simulation, issue, or decision.

design_session_statusA

Read a design session's derived state (stage map, artifacts, ...).

With no session_id the most recently active session is used.

design_session_resumeB

Resume a design run: state plus a plain-language next-action hint.

Surfaces any open blocking question first (answer it before proceeding), otherwise names the next pipeline stage to work on. Use this at the start of a fresh client session to pick up where the last one stopped.

design_next_actionA

Decide the single next action for an autonomous design run.

Reads the session journal and returns the next 13-stage pipeline step server-side, so a client need not memorize the workflow: loop "call design_next_action -> do what it says -> design_session_log the result" until status is complete or blocked.

Returns a dict with status (proceed | retry | blocked | complete), the stage, its goal, suggested_tools (exact tool names), the exit_gate that marks it done, and, on blocked, the open_question to put to the user. Bounded retries: a stage that fails repeatedly escalates to blocked instead of looping forever.

The layout stages (placement, routing, pours_tuning, verification) have measured gates: the reply also carries gate, the numbers and the values that pass, and last_gate, the verdict on numbers already logged for the stage. A stage logged ok whose logged numbers fail its gate comes back as retry, with the failing numbers in guidance. A stage logged with no numbers is taken as logged.

design_autonomy_guideA

The autonomous spec-to-board loop protocol, in one call.

Returns how to drive a full design run with the harness: the loop (start session → design_next_action → execute → log → repeat), the 13 pipeline stages with their tools and exit gates, the hard constraints, and how to resume after context loss. Read this once before an autonomous run; design_next_action then guides each step.

design_session_reportA

Write up a design run from its session journal, as markdown.

Assembled only from what the run recorded: the requirement, each stage's outcome with the gate numbers logged for it, decisions, audit results, simulations and open issues. The body sections come in a fixed order: placement strategy, stack-up and impedance, critical routes (lengths, skews, layer changes, return vias, exceptions), power paths, planes and fan-out (where via-in-pad was used and why), silkscreen policy, verification results, simulations, open issues. A section nothing was logged for says so in one line. Notes reach a section through their topic (see design_session_log).

Args: session_id: the session to report; blank uses the most recently active one. output_path: also write the markdown to this file. Its folder must exist, and a path inside the installed eda_agent package is refused. Blank writes nothing.

Returns: {"session_id", "markdown", "path"} (path is "" when nothing was written), or {"error": ...}.

pcb_layout_auditA

Measure a board for the layout stages' exit gates. Changes nothing.

Reads the focused board exactly, the way pcb_autoroute does (pcb.get_layout_model), or a saved LayoutBoard JSON, and runs the in-house checks over it. Every check returns counts and itemised findings with coordinates in mils:

  • placement_audit: overlapping bodies on one side, pads of different nets closer than the clearance rule, parts on keepouts or mounting holes, parts off the board.

  • connectivity_summary: nets routed / partly routed / not started, and every unreached pad.

  • drc: the exact clearance check.

  • corner_audit: track junctions bending sharper than 45 degrees.

  • return_via_audit: signal vias with no ground or plane via within max_distance_mils.

  • plane_region_audit: pour and plane copper broken into islands.

Log the result as data on the stage_result (design_session_log); design_next_action checks the numbers against the stage's gate.

Args: checks: names from the list above; None runs them all. expect_file: full path of the focused board, for a live read. The read refuses on the first reply if another board is focused. board_json_path: a saved LayoutBoard (.json or .json.gz) to audit offline instead; nothing is read from the editor. nets: the signal nets return_via_audit checks. None checks differential-pair and high-speed-class nets, or every signal via when the board names neither. max_distance_mils: how near a return via must be (centre to centre). Default 40. limit: findings listed per kind; counts are never cut.

Returns: {"board", "pass", "summary": {check: {pass, counts...}}, "checks": {check: full result}, "source": "live"|"file"}, or {"error": ...}.

design_job_startA

Start a long engine run as a background job; returns its id.

For work that can exceed the MCP tool timeout (routing a dense board). Poll design_job_status, then design_job_result when done.

Args: kind: registered job kind. Currently: route (offline A* router; params needs a geometry dict, optional rules / net_classes / grid_pitch_mils). params: keyword arguments for the engine.

Returns: {"job_id": ..., "kind": ...} or {"error": ...} for an unknown kind.

design_job_statusA

Status of a background job (or all jobs if job_id is blank).

design_job_resultB

Fetch a finished job's result payload (None until it's done).

design_snapshot_inventoryA

Open a list of SchLib files and return what components live in them.

NDA scope: only pass paths to the user's own neutral standard libraries. Do NOT pass project-local library paths from another client engagement, design knowledge cannot cross NDA boundaries.

Large libraries (passives/connectors can hold hundreds of parts) easily exceed the tool-output token budget, so the RETURNED set is filtered and capped. The FULL inventory is always cached to <workspace>/inventory.json for the dashboard regardless of these limits.

Args: library_paths: Absolute paths to .SchLib files to scan. name_filter: Case-insensitive substring; keep only components whose lib_ref or description contains it (e.g. "10k", "NE555"). Empty = no filter. limit_per_library: Cap components returned per library (default 60; 0 = unlimited). Each library reports total and returned so you know if it was capped. include_descriptions: Drop the (often long) description field from the returned rows to save tokens. Default True.

Returns: Inventory dict: {"libraries": [{"path", "total", "returned", "components": [...]}]}. Each component carries lib_ref, designator_prefix, pin_count, description (unless suppressed), and footprint when available.

design_validate_planA

Validate a candidate DesignPlan JSON against the schema + cross-check.

Run this before design_execute_plan to catch schema problems and cross-references that the executor will reject. Cheap; no Altium round-trip.

Args: plan_json: Either a JSON string of the DesignPlan, or the DesignPlan as a JSON object/dict. The MCP framework auto-deserializes JSON-object literals to dicts before the tool sees them, so both shapes are accepted.

Returns: {"ok": True, "summary": "..."} on success, or {"ok": False, "errors": [...]} listing the specific problems. The planner can read these and revise.

design_list_circuit_blocksA

List the canonical circuit blocks and their parameter contracts.

Discoverability for design_add_circuit_block: returns every block with its summary, required params, optional params (already folding in the universal sheet / lib_path / pins), and which nets it creates. Call this to author with the exact parameter names instead of guessing -- a mistyped optional key is silently ignored by the block, so getting the names right up front avoids an incomplete part the ERC can't catch. Pure Python, no Altium.

Returns: {ok, blocks: [{name, summary, required, optional, creates_nets}]}.

design_add_circuit_blockA

Fold a canonical circuit block into a DesignPlan in one call.

Authoring boilerplate sub-circuits pin-by-pin in plan JSON is slow and error-prone (a decoupling network is N caps, 2N pins, and two net merges that must line up). This emits the canonical topology of a common block and wires it into plan_json -- allocating unique refdes, tying every pin to the right net, and tagging power/ground and roles -- then returns the augmented plan plus an inline re-validation so the next authoring step starts from a known state.

It is naming-agnostic: it owns the wiring pattern, never the part choice. YOU supply every part identity in params (lib_ref/value/footprint/mpn/datasheet_url), picked semantically from the live inventory snapshot. Pass computed values from design_compute_component_value where relevant.

Blocks and their required params (all also accept pins to override default pin names, refdes_prefix, sheet):

  • decoupling power_net, ground_net, lib_ref [, count, value, footprint] -- N bypass caps.

  • pullup signal_net, rail_net, lib_ref [, value].

  • pulldown signal_net, ground_net, lib_ref [, value].

  • series_resistor net_a, net_b, lib_ref [, value].

  • voltage_divider rail_net, ground_net, lib_ref_top, lib_ref_bottom [, value_top, value_bottom, output_net] -- fresh midpoint net.

  • rc_lowpass input_net, ground_net, lib_ref_r, lib_ref_c [, value_r, value_c, output_net].

  • rc_highpass input_net, ground_net, lib_ref_r, lib_ref_c [, value_r, value_c, output_net] -- series C + shunt R (AC coupling).

  • led_indicator anode_net, cathode_net, lib_ref_r, lib_ref_led [, value_r, mid_net].

  • crystal xin_net, xout_net, ground_net, lib_ref, cap_lib_ref [, value, cap_value] -- crystal + a matched pair of load caps (one shared cap_value keeps the legs equal).

  • pi_filter input_net, ground_net, lib_ref_l, lib_ref_c [, value_l, value_c, output_net] -- shunt C, series L/ferrite, shunt C.

  • mosfet_low_side control_net, load_net, ground_net, lib_ref_r, lib_ref_fet [, value_r_gate, value_r_pulldown, pulldown, fet_pins] -- gate series R + gate pull-down + FET switching a load low-side.

  • mosfet_high_side control_net, load_net, rail_net, lib_ref_r, lib_ref_fet [, value_r_gate, value_r_pullup, pullup, fet_pins] -- gate series R + gate pull-up + PMOS switching a load high-side (source at the rail).

Call design_list_circuit_blocks for the full per-block parameter contract.

Net endpoints the block does NOT own (a pullup's signal, an RC input, a divider rail) should already exist in the plan; the block only creates the nets it fully populates. If you point a block at a net that ends up with a single pin, the inline ERC reports it as a floating net for you to complete.

Args: plan_json: the DesignPlan as a JSON string or dict. block: block name (see the list above). params: block parameters (see per-block requirements above).

Returns: {ok, plan, added: {refdes, nets, extended_nets}, notes, validation} where plan is the augmented DesignPlan dict ready for the next design_add_circuit_block or design_execute_plan, and validation is the same payload design_validate_plan returns. ok is False on a bad block name / missing param / schema error, with errors.

design_add_partA

Add one part and wire its pins to named nets, in one call.

The atomic authoring primitive under the block/bus tools: place a single arbitrary part -- an MCU, a connector, a regulator -- and fold each pin into a net. connections is a pin->net map, the shape a datasheet pinout reads in; pins that map to the same net (an IC's five VCC pins) merge onto one net automatically, so you never hand-maintain a net's pin list. Chain with design_add_circuit_block (peripherals) and design_connect_bus (wide interfaces) to author a whole netlist without writing raw net JSON.

Naming-agnostic: you supply the part identity and net names chosen from the live inventory; this owns only the pin->net folding.

Args: plan_json: the DesignPlan as a JSON string or dict. refdes: the new part's designator (must be free in the plan). lib_ref: its library symbol name. connections: {pin: net_name} -- a net is created if absent or merged into if present. value / footprint / lib_path / sheet / manufacturer / mpn / datasheet_url / role: standard Part fields. power_nets / ground_nets: net names to flag power / ground when this call creates them (an existing net keeps its flags). net_roles: {net_name: role} for nets created here.

Returns: {ok, plan, added: {refdes, nets, extended_nets}, notes, validation} -- same shape as design_add_circuit_block. ok is False on a duplicate refdes / empty connections / schema error.

design_connect_busA

Wire a parallel bus across two+ parts in one call.

A wide interface -- an 8-bit data bus between an MCU and a display, an address bus to a memory -- is otherwise N near-identical net objects where bit i of one side is easily misaligned with bit i+1 of the other. This joins the i-th pin of every endpoint into one net per bit, so alignment is structural and the whole bus is one call. It creates NO parts: the endpoints reference parts already in the plan. The nets share one part-set -- the signature the pipeline uses to DRAW a >=4-bit bus as a bus glyph automatically.

Args: plan_json: the DesignPlan as a JSON string or dict. endpoints: one dict per part on the bus, {"refdes": str, "pins": [pin, ...]}. Every endpoint must list the same number of pins (the bus width); bit i joins pins[i] of each endpoint. net_names: explicit name per bit (length must equal the width); omitted -> f"{net_prefix}{start_index+i}" (D0, D1, ...). net_prefix: prefix for auto-named bits. start_index: first bit number for auto-naming.

Returns: {ok, plan, added: {nets, extended_nets}, notes, validation} -- same shape as design_add_circuit_block. ok is False on a bad endpoint / width mismatch / schema error.

design_compose_netlistA

Apply many authoring operations to a plan in ONE call.

The bulk form of the authoring primitives -- the same reason you prefer a batch Altium tool over looping the singular one. Instead of one round-trip per part/block/bus, pass an ordered list and this threads the plan through all of them, then validates once. Each operation sees the previous one's result, so a part added early is available to a bus wired later. Build a whole board in a single call.

Args: plan_json: the DesignPlan as a JSON string or dict. operations: ordered list of op dicts, each with an "op" key:

    * ``{"op": "add_part", "refdes", "lib_ref", "connections",
      ...}`` -- plus any optional add-part field (``value``,
      ``footprint``, ``lib_path``, ``power_nets``,
      ``ground_nets``, ``net_roles``, ...).
    * ``{"op": "add_block", "block", "params"}`` -- see
      ``design_list_circuit_blocks`` for each block's params.
    * ``{"op": "connect_bus", "endpoints", "net_names"?,
      "net_prefix"?, "start_index"?}``.

Returns: {ok, plan, added: {refdes, nets, extended_nets}, notes, validation} for the FINAL plan -- same shape as the single-op tools. On a bad operation, {ok: False, errors: [...]} where the message names the failing operation's index.

design_generate_bomA

Derive the bill of materials from a plan's parts in one call.

Every design needs a BOM, and it is mechanical to build from parts you already authored -- this rolls them up so you do not hand-group refdes (redundant with the parts, and easy to miscount). Parts with an mpn consolidate by (manufacturer, mpn) -- one orderable line each; parts without an mpn consolidate by (lib_ref, value, footprint) -- so every 100 nF 0402 cap is one line. Deterministic (natural-sorted, R2 before R10). Pure Python, no Altium.

lines_without_mpn flags lines that still need a manufacturer part number before the BOM is orderable -- the gaps to fill.

Args: plan_json: the DesignPlan as a JSON string or dict. set_on_plan: when True (default), the returned plan carries the generated BOM in its bom field, ready for design_execute_plan; the parts are unchanged.

Returns: {ok, bom: [{refdes_list, manufacturer, mpn, description, qty}], summary: {line_count, total_parts, lines_without_mpn}, plan?}.

design_edit_planA

Edit an existing plan -- the MODIFY complement to the add tools.

Authoring is iterative: a review turns up a wrong value, a part to drop, two nets that should be one. Fix it with edits instead of re-emitting the whole plan, and let the tool own the error-prone bookkeeping (deleting a part scrubs it from every net; merging nets moves and de-dupes pins). Ordered ops, each sees the previous result; validates once at the end so a now-floating net surfaces.

Operations (each a dict keyed by "op"):

  • {"op": "set_part", "refdes", <field>: <value>, ...} -- change value / footprint / mpn / manufacturer / lib_ref / status / sheet / role / ... on a part.

  • {"op": "delete_part", "refdes"} -- remove a part; nets it emptied are dropped, nets left with one pin are flagged in notes as floating.

  • {"op": "set_net", "net", <flag>: <value>, ...} -- change a net's is_power / is_ground / role / force_label / force_wires (fix a forgotten power/ground flag).

  • {"op": "connect_pin", "net", "refdes", "pin"} -- tie a part's pin to a net (fix an unconnected pin); makes the net if new.

  • {"op": "disconnect_pin", "net", "refdes", "pin"} -- remove a pin from a net.

  • {"op": "rename_net", "old", "new"} -- rename a net.

  • {"op": "merge_nets", "into", "from"} -- fold from into into (keeping into's flags), then drop from.

Args: plan_json: the DesignPlan as a JSON string or dict. operations: the ordered edit ops.

Returns: {ok, plan, notes, validation} for the edited plan -- same validation shape as the add tools. On a bad op, {ok: False, errors: [...]} naming the operation's index.

design_compute_component_valueA

Compute a manufacturable component value, snapped to an E-series.

Use this instead of doing the arithmetic by hand: it applies the standard sizing equation, snaps to the nearest IEC 60063 preferred value, and reports the ACHIEVED result plus the error so you can see if the snap is good enough or a tighter series is needed.

Args: kind: Which calculation: - "nearest": snap value to the nearest preferred value. - "feedback_divider": regulator FB divider v_out = v_ref*(1+Rtop/Rbot); needs v_out, v_ref; optional r_bottom_ohms to fix the low side. - "resistor_divider": unloaded divider v_out = v_in*Rb/(Rt+Rb); needs v_in, v_out. - "led_resistor": series resistor; needs v_supply, v_forward, i_led_ma. - "rc_lowpass": first-order RC; needs f_cutoff_hz and exactly one of r_ohms / c_farads. - "crystal_load_caps": symmetric crystal load caps; needs c_load_pf (datasheet CL), optional c_stray_pf (default 5 pF per leg). - "i2c_pullup": bus pull-up window (NXP UM10204); needs v_bus, c_bus_pf, t_rise_ns (1000 standard / 300 fast / 120 fast-plus). - "divider_tolerance": worst-case output window of an unloaded divider; needs v_in, r_top_ohms, r_bottom_ohms, optional tol_pct (default 1). - "opamp_gain": Rf/Rin (or Rg) for a gain stage; needs gain and config (inverting / non_inverting). - "buck_inductor": buck inductor; needs v_in, v_out, i_out_a, f_sw_khz, optional ripple_pct (default 30). - "capacitor_energy": stored energy; needs c_farads, voltage. - "holdup_cap": bulk hold-up capacitance; needs i_load_a, t_s (hold-up time), v_drop (allowed sag). - "discharge_resistor": bleeder; needs c_farads, v_initial, v_final, t_s (discharge time). - "junction_temp": Tj = Ta + P*theta_JA; needs power_w, theta_ja, optional t_ambient (default 25). - "max_power": thermal derating; needs tj_max, theta_ja, optional t_ambient. - "required_theta_ja": package/heatsink sizing; needs power_w, tj_max, optional t_ambient. series: E-series name (E6/E12/E24/E48/E96). Default E96 for dividers/precision, pass E24 for jellybean R/C.

Returns: {"ok": True, ...fields..., "summary": "..."} or {"ok": False, "error": "..."}.

design_describe_circuitsA

Report the electrical behaviour of each recognised sub-circuit.

Recognises the parametric blocks in a DesignPlan (voltage / feedback dividers, RC low/high-pass filters, crystal load networks) and computes each one's characteristic from the chosen component values: a divider's ratio, a filter's cut-off, a crystal's load capacitance. Use it to verify a block does what you intended -- it catches the wrong-but-consistent value error (a divider of two valid resistors that produces the wrong ratio) that connectivity and equality checks miss.

Most parameters are ratios / time-constants that need only the values, not a supply voltage, so this works on the plan alone. No Altium.

Returns {"ok": True, "circuits": [{motif, parts, summary, params}]}; blocks whose values are missing or unparseable are skipped.

design_suggest_diff_pair_tracesA

Recommend a controlled-impedance trace width for every differential pair in a plan.

Detects the differential pairs (nets with role differential) and sizes each to target_ohms (90 USB / 100 HDMI/LVDS) for the supplied stackup and edge-to-edge spacing_mils via the IPC-2141 impedance inverse -- the trace geometry for every pair in one call instead of identifying pairs and running pcb_calc_trace_width_for_impedance by hand. The stackup and target are board-level decisions you supply; pure Python, no Altium.

Returns {"ok": True, "pairs": [{nets, endpoints, width_mils, spacing_mils, target_ohms, feasible}]} or {"ok": False, ...}.

design_review_fileA

Offline FALLBACK review of a .SchDoc/.PrjPcb: OFF by default.

This is NOT the preferred way to review a design. It parses the Altium file directly (OLE reader) with no running Altium and no license, so it only covers the netlist-free, component-level subset (missing MPN / datasheet / manufacturer, placeholder values, designator collisions, missing / unannotated designators, incomplete title block). It cannot compile a netlist or run ERC, and an offline parser can misread a file.

Prefer the live-Altium tools whenever a session is available: design_lint_report, proj_run_erc, design_review_snapshot, and the audit_* family run Altium's own engines and see connectivity this reader cannot. Reach for this tool only when Altium genuinely can't be opened (a file on disk, a CI runner).

Because of that, this surface is disabled by default. To enable it, set the environment variable EDA_AGENT_HEADLESS_REVIEW=1. Without it the tool returns an error explaining the opt-in.

Args: path: a .SchDoc sheet or a .PrjPcb project (reviews all its sheets).

Returns the review report {file, component_count, findings, summary, ...}: or {"error": ...} if disabled or unreadable.

design_solve_netlist_fileA

Reconstruct a .SchDoc's netlist geometrically: OFF by default.

Offline FALLBACK, same opt-in as design_review_file. Parses the file (no Altium, no license) and rebuilds the compiled netlist from geometry (pins, wires, power ports, junctions, and by-name net labels), then runs connectivity ERC (single_pin_net floating pins, net_short rail shorts).

Prefer the live-Altium path (proj_get_nets / proj_run_erc) whenever a session is available: Altium's own compiler is ground truth. Use this only when Altium can't be opened.

Validated envelope: wire + power port + junction + net-label connectivity (live Altium 24/24; pipeline buck 7/7). It faithfully reports a net the schematic left floating; cross-sheet connectors are out of scope. A single sheet only (a .SchDoc), not a project.

Enable with EDA_AGENT_HEADLESS_REVIEW=1.

Returns {file, net_count, nets, pin_count, findings}: or {"error": ...} if disabled or unreadable.

design_bom_fileA

Consolidated BOM from a .SchDoc/.PrjPcb on disk: OFF by default.

Offline FALLBACK, same opt-in as design_review_file. Parses the Altium file directly (no Altium, no license) and groups components into purchasable BOM lines, one per distinct (mpn, value, lib_reference), designators grouped and naturally sorted, quantity summed. A .PrjPcb aggregates all its sheets.

Prefer the live-Altium proj_get_bom / proj_export_bom_html when a session is available. Use this for a file on disk or a CI artifact.

Enable with EDA_AGENT_HEADLESS_REVIEW=1.

Returns {file, line_count, part_count, lines} where each line is {quantity, designators, mpn, manufacturer, value, lib_reference, datasheet}: or {"error": ...} if disabled or unreadable (missing file, a suffix other than .SchDoc/.PrjPcb, or a project whose sheets cannot be resolved). A design read with zero parts adds a warning: an empty BOM is not proof of an empty design.

design_review_planA

One-call offline pre-flight: bundle every plan-level analysis.

Runs, on the plan alone (no Altium), the checks and reports the agent would otherwise call one by one, so the planner can vet a design in a single step before emit:

  • stats: part counts by kind, IC / passive split, power & ground rails, average net degree, the widest signal net (a routing hotspot).

  • erc: the ERC-lite report (floating nets, unconnected parts, missing decoupling, malformed values, matched-value mismatches); passed is False only on a hard error.

  • circuits: the computed behaviour of each recognised block (divider ratios, filter cut-offs, crystal load).

  • placement_constraints: the match / keepout groups that would be auto-derived for pcb_plan_placement(plan_json=...).

  • net_classes: each net's class (power / ground / differential / clock / analog / ... / signal) grouped for PCB net-class setup.

Returns {"ok": True, "passed": <erc.passed>, ...sections...} or {"ok": False, "errors"/"error": ...} on a schema problem.

design_layout_schematicA

Compute the schematic layout for a DesignPlan, as pure data.

THE ONE SCHEMATIC ENGINE. Every schematic tool now runs the same canvas pipeline, so whenever you are asked to draw or lay out a schematic, reach for this family and nothing else: this tool for the geometry as data, design_preview_plan for the same layout rendered to SVG, design_execute_plan to place it in Altium. There is no longer a second engine to choose between.

It used to run a second, standalone engine whose placements and score described a layout nobody would ever see. That engine is no longer reachable: measured over ten held-out corpus sheets it lost the shared objective on seven and produced four pairs of OVERLAPPING BODIES where this pipeline produced none, so the shorter wire it appeared to draw was partly bought by stacking parts on top of one another.

Runs OFFLINE and never reaches Altium, so it works on every backend. That costs one thing and the result says which: symbol geometry is synthesised from the plan's pin lists rather than read from a library, so symbols comes back "synthetic" and execution_accurate is False. The placement and routing RULES are the ones that execute. When you need the exact geometry, use design_preview_plan (same pipeline, real symbols, renders SVG) or design_execute_plan to emit.

Args: plan_json: A DesignPlan as a JSON string or a JSON object/dict. sheet: Sheet name to report (default "main"). grid_mils: Accepted for compatibility; the canvas pipeline snaps to its own 100-mil grid. fr_iterations: Accepted for compatibility; the pipeline sweeps its own force-directed budget and scores the results. placement_hints: Optional {refdes: {"x", "y", "rotation"}} pinned positions, same as design_execute_plan. render_png: Optional path for a preview image of the layout.

Returns: Dict with ok / sheet / engine ("canvas") / execution_accurate (True) / summary / placements / net_representation / wires / net_labels / power_ports / junctions / score / notes / failures. On a bad plan: {"ok": False, "errors": [...]}.

design_suggest_partitionA

Suggest how to split a design into balanced functional groups.

Computes a min-cut partition (Kernighan-Lin style) of the plan's components into n_groups balanced groups that MINIMISE the number of nets crossing between groups -- so each group is internally well-connected and few signals span the boundary. Use it to group a PCB into functional ROOMS, or to decide how to break a design with SEPARABLE blocks across schematic sheets.

Note: a HUB-centric design (one IC fanning out to many peripherals) does not separate cleanly -- the hub is connected to everything, so the cut stays high and per-sheet density barely drops. Partitioning helps when the netlist has genuinely distinct functional blocks; read cut_nets (low = clean) and group_sizes to judge before acting.

Power/ground rails (nets touching more than max_fanout parts) are excluded from the connectivity graph -- they route as planes and connect nearly everything, so the split follows the SIGNAL structure.

Pure data; touches nothing. Assign the returned groups to part sheet (or zone) fields yourself if you act on the suggestion.

Args: plan_json: A DesignPlan (dict or JSON string). n_groups: Number of groups to split into (default 2). max_fanout: Nets above this pin count are treated as rails and ignored for partitioning (default 8).

Returns: {ok, n_groups, groups: {idx: [refdes...]}, cut_nets, boundary_nets: [names...], group_sizes, summary}. cut_nets is how many nets cross the boundary (lower is cleaner); boundary_nets names them -- those become cross-sheet labels or off-sheet connectors if you split there.

design_execute_planA

Instantiate a DesignPlan in Altium.

Two execution paths:

  • use_canvas=True (default): the new canvas-based pipeline. Plan -> SymbolExtractor -> SchematicCanvas (all in Python) -> one-shot batched emit to Altium. Layout decisions are made before any IPC; an SVG preview is written next to the project file so you can sanity-check the schematic without opening it.

  • use_canvas=False: the legacy executor that interleaves layout with Altium IPC. Kept as a fallback while the new path stabilises.

Both halt early if the plan contains any needs_creation parts. Resolve those first by either picking an existing-lib equivalent or branching into a library-authoring sub-task.

Args: plan_json: Either a DesignPlan JSON string, or the DesignPlan as a JSON object/dict. The MCP framework auto-deserializes JSON-object literals to dicts before the tool sees them, so both shapes are accepted. project_path: Absolute path to the target .PrjPcb. Created if it does not exist. use_canvas: Pick the execution path. Default True runs the new canvas pipeline; pass False to fall back to the legacy executor. placement_hints: Optional {refdes: {"x": int, "y": int, "rotation": int}} partial anchors. Hinted refdes pin to the supplied position; others run through the algorithmic placement (Sugiyama + multi-try scoring). Used by the agent-in-loop refinement workflow: 1. Run design_preview_plan -> see SVG + score. 2. If layout is bad, identify specific refdes that should sit elsewhere; build hints dict. 3. Call design_preview_plan again with hints -> iterate until score is acceptable. 4. Call design_execute_plan with the same hints to emit the refined layout. mode: How to reach the sheet. "auto" (default) EDITS a sheet this tool has drawn before, moving only the parts whose position changed, placing the new ones, deleting the ones the plan dropped, and leaving every other component exactly as it is. It draws from scratch when there is no prior sheet. This is what makes re-running a changed plan safe on a schematic somebody has worked on. "full" re-places every component, discarding any hand edit; use it when you want the sheet redrawn. "delta" refuses rather than redraw.

    The edit path only knows what is in the plan. To keep a
    part where a person dragged it, read the sheet back with
    ``design_hints_from_sheet`` and pass the result as
    ``placement_hints``.

Returns: Result dict with ok / project_path / sheets_touched / placed (list of placements) / failures / needs_creation / notes. Canvas-path additions: canvas (the SchematicCanvas dict snapshot) and preview_svg_path (where the SVG was written). On an edit, delta lists the refdes added, moved, replaced, removed and untouched; placed then covers only the parts actually placed, which on an edit is the new ones.

design_learn_from_layoutA

Capture your placement edits as training data.

Workflow:

  1. Run design_execute_plan (canvas path): it writes a <project>.canvas.json snapshot alongside the .PrjPcb.

  2. Open the schematic in Altium, drag components to taste, save.

  3. Call this tool. It reads the snapshot + current Altium positions, diffs them, and appends one row per moved component to %USERPROFILE%\.eda-agent\placement_edits.jsonl.

Each row carries: design_id, refdes, part_role, part_lib_ref, anchor_refdes, anchor_role, anchor_lib_ref, dx_mils, dy_mils, rot_delta_deg, design_size, ts.

Anchor = highest-pin-count netlist neighbor on a non-power / non-ground net (or spatial-nearest fallback). Captures the relational placement preference, not just "I moved this 200 mils right".

The accumulating log feeds placement_priors.json (the relative-anchor priors used by the pipeline's placement pass).

Args: project_path: Same project path you passed to design_execute_plan.

Returns: Dict with ok, rows_appended, refdes_moved, refdes_unchanged, log_path, notes.

design_plan_from_sheetA

Read a DesignPlan back off a schematic, including one you did not draw.

Use this when you are asked to change a schematic that has no <project>.canvas.json beside it: a sheet drawn by hand, or one from an older project. It gives you the plan the rest of the design tools take, so an edit becomes "change the plan, lay it out again" instead of dragging symbols around one at a time.

The plan is reconstructed from what is actually drawn: the components give the parts, the compiled netlist gives the nets, and the power-port glyphs say which nets are rails. It is validated before it is returned.

WHAT A SCHEMATIC DOES NOT RECORD, so you must supply it:

  • role on each part. This is the tag that lets the placer recognise a decoupling bank, a crystal cluster, an op-amp motif. A plan without roles lays out noticeably worse. Read the returned part list and netlist and assert the roles yourself before laying it out.

  • zone on each part, if you want functional blocks.

  • Single-pin nets, which the plan schema cannot express. They come back in dropped_pins.

Then: edit the plan, call design_layout_schematic to see it, and design_execute_plan to apply it. Pass design_hints_from_sheet output as placement_hints if you want the existing positions kept.

Args: project_path: The .PrjPcb the sheet belongs to. Its compiled netlist is what supplies the nets. sheet_document: Full path to the .SchDoc to read. Defaults to the active document.

Returns: ok / plan (a DesignPlan dict, or None on a refusal) / parts_read / nets_read / dropped_pins / notes.

design_hints_from_sheetA

Current sheet positions, as placement_hints for a re-run.

THIS IS HOW YOU EDIT A SCHEMATIC WITH THE ENGINE. Editing by hand means choosing coordinates, which is what sch_place_components refuses. Do this instead:

  1. design_edit_plan to change the plan (add the part, change a value, add a net).

  2. design_hints_from_sheet to capture where everything currently sits.

  3. design_execute_plan with those hints. Every existing part stays exactly where it is, and only the new or changed ones are placed, by the engine rather than by you.

Positions come back as BODY CENTRES, the frame placement_hints expects, converted from the symbol origins Altium reports. Pass the returned hints through unchanged; do not round or adjust them, and drop a refdes from the dict only when you deliberately want the placer free to move that part.

Needs the <project>.canvas.json snapshot that design_execute_plan writes, which is what records the library symbol behind each refdes.

Args: project_path: Absolute path to the .PrjPcb whose sheet to read.

Returns: ok / hints ({refdes: {x, y, rotation}}) / unmatched (refdes present in the snapshot but not readable now) / notes.

design_preview_planA

Render a DesignPlan to SVG without emitting to Altium.

Same pipeline as design_execute_plan(use_canvas=True) minus the place + wire + save IPC pass. Symbol extraction still consults Altium on cache miss (it has to read the SchLib), but no project is created and nothing is placed. Use this to sanity-check a layout cheaply before committing to a full emit.

Args: plan_json: DesignPlan as JSON string or dict. output_svg_path: Where to write the SVG. Default: <repo>/.symbol_cache/preview.svg. placement_hints: Optional {refdes: {"x", "y", "rotation"}} partial anchors, same as design_execute_plan: hinted refdes pin to the supplied position, the rest run through algorithmic placement. Iterate preview→hints→preview to refine a layout before a full emit.

Returns: Dict with ok / preview_svg_path / canvas (snapshot) / counts {placements, wires, labels, power_ports, junctions} / notes / failures.

design_audit_schematicA

Structured visual/layout audit of the active schematic.

Call AFTER design_execute_plan and BEFORE design_validate so layout problems are fixed first; ERC violations downstream are less noisy. Detects three classes of issue, each with enough geometry for the planner to compute a corrective move:

  • overlaps - pairs of components whose bboxes intersect

  • wire_crossings - wire segments that cross a component body (excluding pin-to-pin connections)

  • stacked_ports - 3+ power/ground glyphs of the same net huddled inside cluster_radius_mils

Args: project_path: Optional .PrjPcb path. None uses the focused project. cluster_radius_mils: Radius for stacked-port clustering. Default 600 mils.

Returns: SchematicAuditReport dict: {ok, project_path, overlaps[], wire_crossings[], stacked_ports[], notes[]}. ok=True iff every list is empty.

design_validateA

ERC + connectivity sanity report on the focused project.

Runs run_erc, project.get_messages, and get_unconnected_pins, then bundles the output into a structured ValidationReport that the planner can read and respond to. Schematic-only in this slice, PCB validation is a separate later slice.

Args: project_path: Optional absolute path to a .PrjPcb. If omitted, uses the focused project.

Returns: ValidationReport dict: {passed, project_path, errors, warnings, notes} where each error/warning is an Issue with {category, severity, text, refdes, pin, net, sheet}.

design_validate_requirementA

Validate a structured DesignRequirement before planning starts.

Stage 1 of the autonomous flow (requirement capture & architecture, reference/autonomy-roadmap.md): capture what the board must do as a DesignRequirement dict, run this gate, and only move on to part selection / DesignPlan construction when it returns ok. Every fact the requirement does NOT state goes into open_questions as a question for the user -- it is never guessed -- and validation fails while any question remains unresolved.

Checks beyond the schema: unresolved open questions, no outputs, no power source, inverted temperature / IO-voltage ranges, comms IO without a protocol, and supply rails or power outputs whose magnitude exceeds every stated power input (a boost / inverting stage the user must confirm). Pure Python, no Altium.

board_intent (stackup, impedance targets, peak currents, fastest edge, enclosure, assembly, finish, test strategy) is all optional and never fails validation. Where the rest of the requirement makes one of them matter and it is missing, notes carries a suggested question to ask the user.

Args: requirement: DesignRequirement as a JSON object/dict. Units are SI with the unit in the field name (voltage_v, current_a, temp_min_c); mechanical dimensions are MILLIMETRES (board_size_mm, height_max_mm).

Returns: {"ok": True, "issues": [], "notes": [...], "summary": "..."} when the requirement is planning-ready (summary is the labelled-line block to embed in DesignPlan.summary), or {"ok": False, "issues": [...]} / {"ok": False, "errors": [...]} (schema problems) listing what to resolve.

design_load_fab_profileA

Validate a fab capability profile and echo the normalized form.

Stage 8 of the autonomous flow (rules & stackup, reference/autonomy-roadmap.md): before synthesizing design rules, the fab's published limits are captured as a FabProfile dict -- transcribed from the fab's capability page (cite it in source), NEVER recalled from memory. This tool is the schema gate: it validates the dict (all dimensions MILS, copper weight oz/ft^2, stackups with copper outer layers and no adjacent copper plies) and returns the normalized profile for design_synthesize_rules.

Args: profile: FabProfile as a JSON object/dict: name, optional source citation, copper_layer_counts, the seven min_*_mils floors, and optional stackups.

Returns: {"ok": True, "profile": {...}, "stackups": [names], "summary": "..."} or {"ok": False, "reason": "..."}.

design_synthesize_rulesA

Synthesize PCB design rules + stackup ops from a fab profile.

Stage 8 of the autonomous flow (rules & stackup, reference/autonomy-roadmap.md): turn the validated fab profile, the plan's net classes, and a few board-level targets into the concrete pcb_create_design_rule and pcb_modify_layer parameter dicts, dispatched verbatim after pcb_create_net_class creates classes named after the synthesized groups. Every number traces to a profile field or a verified calculator (IPC-2221 width inverse, IPC-2141 impedance inverse); a rule whose inputs are missing is skipped with a note, never guessed. Pure Python, no Altium.

Args: profile: FabProfile as a dict (see design_load_fab_profile). plan_json: Optional DesignPlan (JSON string or dict). When given and net_class_map is not, net classes are derived from the plan (flags + net roles, naming-agnostic). net_class_map: Optional explicit {net_name: class} map; takes precedence over plan_json derivation. options: Optional synthesis targets: stackup (name, default first), class_current_a ({class: amps} -> per-class width rules), delta_t_c, track_margin, layer, copper_oz, geometry, diff_pair_target_ohms (required for differential rules), diff_pair_spacing_mils.

Returns: {"ok": True, "rules": [...], "stackup_ops": [...], "notes": [...], "stackup": name|None, "net_classes": {...}} -- rule values are INTEGER MILS -- or {"ok": False, "reason": "..."}.

design_plan_hierarchyA

Propose a multi-sheet hierarchy for a dense DesignPlan.

Stage 6 of the autonomous flow (schematic emit, reference/autonomy-roadmap.md): a plan past ~20 parts stops fitting one readable sheet and dense single sheets are the known short-risk regime. This tool partitions the parts by signal connectivity (min-cut, zones kept atomic), names each child sheet from its dominant zone role, derives the inter-sheet ports from the signal nets the cut severs (power/ground rails are continuous through power ports and never become sheet entries), and emits the top-sheet op list in the exact parameter shapes of sch_place_sheet_symbol / sch_place_sheet_entry / sch_generate_toc. Apply the result to the plan with design_apply_hierarchy before design_execute_plan. Deterministic; pure Python, no Altium.

Args: plan_json: DesignPlan as a JSON string or dict. max_parts_per_sheet: Split threshold (default 20). At or below it the plan stays single-sheet (split=False).

Returns: {"ok": True, "split": bool, "top_sheet": str, "sheets": [{name, refdes, zones, part_count}], "ports": [{net, from_sheet, to_sheet, io_type}], "top_sheet_ops": [{tool, params}], "cut_nets": int} (op coordinates are MILS) or {"ok": False, "reason": "..."}.

design_apply_hierarchyA

Rewrite a DesignPlan onto the sheets a hierarchy proposes.

Stage 6 of the autonomous flow (schematic emit, reference/autonomy-roadmap.md), the second half of the hierarchy step: take the result of design_plan_hierarchy and produce a NEW plan with sheets = [top sheet, *child sheets], every part's sheet re-homed per the hierarchy, and every zone moved with its parts (part-less zones park on the top sheet). The input plan is never mutated; a non-split hierarchy returns the plan unchanged. Feed the returned plan to design_validate_plan and then design_execute_plan; emit the hierarchy's top_sheet_ops separately to draw the top sheet. Pure Python.

Args: plan_json: DesignPlan as a JSON string or dict. hierarchy: The dict returned by design_plan_hierarchy.

Returns: {"ok": True, "plan": {...rewritten DesignPlan...}, "sheets": [names], "summary": "..."} or {"ok": False, "reason": "..."} on a malformed plan or hierarchy.

sch_render_svgA

Render the active SchDoc to an SVG file (in-house renderer).

SCOPE: this is a VISUAL artifact for when an image is genuinely needed (layout/readability review, sharing a picture, a CI thumbnail). It is NOT an input to a design review. NEVER judge connectivity, part values, ratings, or pin functions from this render -- a wire that looks joined may not share a net, and a net can be correct while the drawing looks messy. Connectivity and part facts come from the compiled netlist + part data (proj_get_nets, proj_get_connectivity_many, obj_crossref_net, proj_get_unconnected_pins, proj_get_erc_violations, design_review_snapshot), never from a picture.

Pulls every component, pin, wire, junction, net label, port and power port from the active schematic via the bridge, then emits a self-contained SVG with data-designator, data-net, data-pin attributes on every meaningful group so a downstream consumer (the web dashboard, an LLM tool, a CI check) can hook interaction without re-parsing.

v1 draws components as labelled bounding-box bodies with pin stubs from the body to the electrical end. Full symbol-internal primitives (rectangles / lines / arcs inside each symbol) are deferred to v2 -- the v1 output is recognisable and ready for interactive overlay but does not yet draw the actual symbol art.

Args: output_path: Where to write the SVG. Defaults to the transient <workspace>/renders/ dir (kept out of the workspace root so renders don't pile up in your folder). margin_mils: Padding around the geometry bbox in mils (default 200). flip_y: When True (default) flip the Y axis so the output reads in Altium's conventional bottom-left-origin space rather than SVG's top-left-origin.

Returns: Dict with ok, svg_path, doc, counts (one entry per primitive type).

pcb_render_svgA

Render the active PcbDoc to an SVG file (in-house renderer).

SCOPE: a VISUAL artifact for when an image is genuinely needed (placement / silkscreen / spacing review, sharing a picture). It is NOT an input to a design review's electrical findings. NEVER judge connectivity, net assignment, or clearance from this render -- read those from the board data (pcb_get_nets, pcb_get_unrouted_nets, pcb_get_clearance_violations, pcb_run_drc, proj_compare_sch_pcb). The render shows geometry; the netlist shows connection.

Pulls the board outline, every track, arc, pad, via, and text primitive from the active PCB via the bridge, then emits a per-layer SVG. Layers are grouped in render order (KeepOut at bottom, then bottom copper, top copper, overlays, outline on top, drills last) and every track / arc / pad / via group carries data-net, data-layer, data-shape so a downstream dashboard or LLM tool can cross-probe or net- highlight without re-parsing.

v1 surface: board outline + tracks + arcs + pads (round / rect / octagonal / rounded-rect) + vias + texts on overlays. Regions (polygon fills / copper pours) and component-body silkscreen outlines are deferred to v1.1.

Args: output_path: Where to write the SVG. Defaults to the transient <workspace>/renders/ dir (kept out of the workspace root so renders don't pile up in your folder). layers: Restrict to these Altium layer names (e.g. ["TopLayer", "TopOverlay", "KeepOutLayer"]). When None (default) every known layer is rendered. MultiLayer (vias) and the outline are always drawn. margin_mils: Padding around the board bbox in mils. flip_y: When True (default) flip Y so the SVG reads in Altium's bottom-left-origin space. show_drills: Punch hole circles through pads / vias. show_texts: Render text primitives on overlay layers. show_designators: Draw component designators on the overlay. interactive_legend: Emit a clickable per-layer legend group in the SVG (for the dashboard / cross-probe consumers). background: Board-background fill (defaults to a slate review colour; pass "none" for transparent).

Returns: Dict with ok, svg_path, counts (per primitive kind), bbox (in mils), bytes.

design_visual_reviewA

Render the active design and return it as a viewable image plus a critique rubric -- the first step of a visual self-check loop.

SCOPE: layout and placement ONLY -- schematic readability / spread / flow, PCB part placement, silkscreen, spacing, overlaps. This is NOT a connectivity or part-correctness review. Do NOT use it to decide what connects to what, whether a value/rating is right, or whether a pin is wired -- those come from the netlist + part data (design_review_snapshot, proj_get_nets, proj_get_connectivity_many, proj_get_erc_violations), never from the image. The rubric below is deliberately about geometry; treat every electrical question as out of scope here.

Renderers are exports; this turns them into feedback. It draws the active SchDoc or PcbDoc, rasterizes it to PNG (headless Edge) so you can actually look at it with the Read tool, and returns a document-specific rubric (what to look for) and loop_protocol (render -> look -> critique -> cross-check -> fix -> re-render). Each rubric item names the existing audit tool(s) that measure it exactly, so vision finds candidates and the audits confirm and locate them.

Use it as a loop: call this, Read the returned png_path, critique against rubric, run the named audits for anything suspicious, fix with the normal tools (for PCB placement, pcb_plan_placement), then call this again to confirm. A couple of iterations is usually enough.

Args: target: "auto" (detect the active document), "schematic", or "pcb". output_path: Where to write the SVG (.png sits beside it). Defaults to the transient <workspace>/renders/ dir; the scratch .raster.svg used for the screenshot is deleted automatically (kept out of your folder). rasterize: Also produce a PNG so the image is directly viewable (default True). If no browser is found the SVG is still returned with a rasterize_note. width: Raster width in pixels (height follows the aspect). margin_mils: Override the render margin. layers: PCB only -- restrict to these layer names.

Returns: Dict with ok, target, doc, svg_path, png_path (None if rasterization was skipped/failed), counts, bbox, rubric, loop_protocol, next_step.

proj_export_bom_htmlA

Export the project BOM as a self-contained interactive HTML file.

Standalone HTML page, no external CSS / JS / Altium runtime dependency. Open it in any browser; sortable columns, free-text filter, toggle between grouped (one row per value+footprint) and per-component layouts. A self-contained one-shot static export.

Useful for emailing the BOM to a manufacturer, archiving with a board release, or sharing with a non-Altium reviewer.

Args: output_path: Where to write the HTML. Defaults to <workspace>/bom.html. title: Heading + <title> for the page. limit: Max components to include (default 5000; the BOM call supports up to ~10000 cleanly).

Returns: Dict with ok, html_path, components (count written), bytes.

audit_find_inconsistent_track_widthsA

Find nets where the maximum and minimum track widths differ by more than 2×.

Classic bug pattern: a power rail (VCC / VDD / GND) is routed at 20 mil to handle the expected current, then later someone adds a connection using the editor's default 8 mil width and moves on. The result is a thin orphan stub welded to a wide bus -- under load that thin section becomes a thermal hotspot and eventually a fab rework. Most agents and human reviewers miss it because the bulk of the net looks fine.

Algorithm: per net, collect min/max track widths on signal layers. Ratio > 2.0 → flag. Per-violation reports the actual widths and the (x, y) of the thinnest track on the net so the agent can jump straight to the questionable section.

Pattern: SDK-derived. No community-script reference.

Returns: Dict with {checked, violations, items[]} where each item carries {net, min_width_mils, max_width_mils, ratio, thin_x_mils, thin_y_mils}.

audit_find_acute_anglesA

Find pairs of same-net tracks meeting at < 90° interior angle.

Classic fab DFM check: at an acute corner the etchant pools and over-etches the inside of the bend -- the trace narrows or breaks during fabrication. Most fab houses reject sub-90° corners on critical-thickness traces in their automated DRC, but the rejection comes back days into the schedule; catching it on the desk is much cheaper.

Algorithm (pure geometry, no rule dependency):

  • Walk every track on signal layers

  • At each endpoint, spatial-iterate for other tracks on the SAME net sharing that endpoint

  • Build direction vectors AWAY from the shared point

  • Dot product positive → interior angle < 90° → acute

Capped at 100 violations to keep responses manageable on dense boards. Per-violation: net, layer, x_mils / y_mils of the corner, and the actual angle in degrees.

Pattern: SDK-derived. The Altium IPCB_AcuteAngleRule exists but isn't always enabled by default and its violations only surface during DRC; this audit runs independently of rule state.

Returns: Dict with {checked, violations, items[]} where each item carries {net, layer, x_mils, y_mils, angle_deg}.

audit_find_placeholder_valuesA

Find SCH component parameters containing obvious "I'll fix this later" placeholder strings that escaped a release.

Matched values (case-insensitive, whole-string after trim):

  • TBD, TBA

  • TODO, FIXME, XXX

  • ?, ??, ???

  • PLACEHOLDER, FILLER, ASK, UNKNOWN

  • N/A, NA

Classic bug pattern: an EE drops a 100nF cap as a temporary placeholder while working out a regulator topology, types "TBD" in the Comment, moves on, never comes back. Six months later the board ships to fab with "TBD" on the assembly drawing. The fab calls confused, the schedule slips.

Checks every parameter (Comment, Value, Manufacturer, MPN, Description, etc.) on every component across the project. Skips empty values -- those are caught by other audits (e.g. audit_find_missing_datasheets).

Pattern: SDK-derived. No community-script reference; this is a defensive sanity check we should always run before release.

Returns: Dict with {checked, violations, items[]} where each item carries {designator, parameter, value} for a component parameter that matched a placeholder pattern. checked counts ALL parameters inspected.

audit_find_orphan_power_objectsA

Find schematic power-port markers (GND, VCC, +3V3 etc.) sitting off-wire.

Symmetric cousin to audit_find_orphan_net_labels. A power port carries its net name (from the marker's Style); the marker has to sit ON a wire for the wire to actually adopt that net. Markers in empty space create phantom power connections -- the schematic LOOKS like the rail is hooked up, but the wire underneath is unrelated.

ERC sometimes catches this when the wire ends up with no driver, but power rails frequently have multiple drivers (the real connection elsewhere, plus this phantom), so ERC sees a valid net and stays quiet.

Spatial-iterates a 1-mil square at each ePowerObject location for any eWire; absence → orphan.

Pattern: SDK-derived, mirrors the orphan-net-label check.

Returns: Dict with {checked, violations, items[]} where each item carries {net, sheet, x_mils, y_mils} for an orphan power object. net comes from the marker's Text (its visible net name).

audit_find_orphan_net_labelsA

Find schematic net labels whose location is NOT on any wire or bus.

In Altium, a net label labels whichever wire passes through its (x, y). If the label sits in empty space (because the wire was moved during a layout pass, or was never placed), the compile produces a phantom net -- the label looks correct on paper but the signal it "names" connects to nothing under the label. ERC sometimes catches this (no driver / no load) but not always -- the other end of the same-named net often provides a load, so the violation never fires.

Spatial-iterates a 1-mil square at each net label's location for any eWire / eBus object. Absence → orphan.

Pattern: SDK-derived. No community-script reference; surfaced by reviewing the ISch_NetLabel interface in the Altium Schematic API docs.

Returns: Dict with {checked, violations, items[]} where each item carries {label, sheet, x_mils, y_mils} for an orphan net label.

audit_find_net_label_conflictsA

Find net labels that silently merge, short, or do nothing.

Three failure modes that all look correct on a printed sheet:

  1. conflicting_labels -- two labels with DIFFERENT text at the same Location (x, y), not merely overlapping text boxes. Altium merges both names into one net and one name wins; the losing net ceases to exist and everything on it is absorbed. This is a real short between two named nets, and the usual symptom is "net X has zero pins" while some unrelated pin turns up on net Y. Adjacent labels on a 100-mil pin pitch are not conflicts.

  2. labels_on_pin_root -- a label sitting on a pin's Location instead of its electrical end. Location is the BODY-side root; a pin connects at Location + PinLength along Orientation (0=right, 1=up, 2=left, 3=down). A label on the root is inert, so the sheet reads as fully wired while the pin floats on an auto-generated net. Each item reports the label's coordinates AND the connect_x_mils / connect_y_mils the label should move to.

  3. duplicate_labels -- same text twice at one point. Harmless electrically, but it is clutter and it hides class 1 underneath.

Complements audit_find_orphan_net_labels, which only asks whether a wire sits under the label and therefore reports genuinely-connected labels (those on a pin end, with no wire) as orphans.

Returns: Dict with {checked, conflicts, on_pin_root, duplicates, conflicting_labels[], labels_on_pin_root[], duplicate_labels[]}.

audit_find_visible_supplier_pnA

Find schematic components with a VISIBLE supplier-PN parameter.

BOM hygiene rule: only the manufacturer's MPN should appear on the SCH PDF. Supplier part numbers (Digi-Key, Mouser, Newark, Arrow, RS, Farnell) change over time -- a Digi-Key bin gets retired, the part is re-binned under a new Digi-Key SKU, and an SCH PDF saying "Digi-Key 296-1234-1-ND" sends the next person picking the BOM five years from now on a wild goose chase. Manufacturer + MPN is stable; supplier PNs are not.

Detects any component parameter whose name starts with "supplier" (case-insensitive) AND IsHidden=False. Catches the legacy "Supplier Part Number 1" along with any free-text "Supplier" / "Supplier 1" variants designers create.

Returns: Dict with {checked, violations, items[]} where each item carries {designator, parameter, value} for a visible supplier-PN parameter. Pair with sch_set_components_parameters to hide them in bulk.

audit_find_unlocked_component_primitivesA

Find placed components whose internal primitives are NOT locked. Unlocked primitives are a silent fab-bug class -- a stray click + drag in the PCB editor moves a single pad off-center within the footprint without any warning. The gerbers look fine, just slightly off, and the part doesn't solder properly because the pad no longer aligns with the device's lead.

Library footprints ship with PrimitiveLock=True by default; components show up unlocked when someone clicked Component Properties and toggled the flag off (sometimes deliberately to nudge silkscreen, more often accidentally).

Returns: Dict with {checked, violations, items[]} where each item carries {designator} for an unlocked component. Pair with obj_batch_modify to re-lock at scale.

audit_find_mirrored_pcb_textA

Find free-floating PCB text (eTextObject) that's mirrored on the wrong overlay.

Rule: top-overlay text must read normally; bottom-overlay text must be mirrored. When the assembled board is flipped to access the bottom side, the bottom text reads correctly. A normally-oriented bottom-side text reads backwards on the physical board -- the kind of thing that gets caught only at the fab-review stage by the customer's QA, then triggers a respin.

Note: this checks free-floating text (revision strings, layer labels, copyright notices) -- NOT component designators or comments, which Altium auto-flips with the component.

Returns: Dict with {checked, violations, items[]} where each item carries {text, layer, reason}. reason is one of top_overlay_text_is_mirrored or bottom_overlay_text_is_not_mirrored.

audit_find_mixed_designator_rotationA

Find PCB silkscreens where designators face BOTH 0 and 180 (or both 90 and 270) -- the assembly-readability check.

Assembly inspectors and pick-and-place operators read silkscreen from a fixed angle. If half the designators on the top overlay read normally and the other half are upside-down (0 vs 180), QC has to physically rotate the board between parts -- slow, error-prone, and the kind of thing fab houses charge extra to deal with. Orthogonal mixes (0 + 90) are fine because the inspector turns their head, not the board.

The check runs per overlay layer (top, bottom) independently -- a top side with mixed 0/180 doesn't excuse a clean bottom.

Returns: Dict with {checked, violations, top_mixed_0_180, top_mixed_90_270, bottom_mixed_0_180, bottom_mixed_90_270, items[]}. Each item carries {designator, layer, rotation_deg} for a component contributing to a flagged pair, so the agent can rotate those specifically.

audit_find_non_embedded_imagesA

Find schematic images that are NOT embedded -- they hold only a path reference to the original file.

Non-embedded images render fine on the designer's own machine but show a red X / placeholder box on every other machine that opens the schematic, because the path doesn't resolve. Common offenders are company-logo title-blocks dragged in from a network share and never re-saved with EmbedImage=True. The manufacturer / customer sees broken images on the SCH PDFs.

Returns: Dict with {checked, violations, items[]} where each item carries {sheet, x_mils, y_mils} for a non-embedded image. checked counts ALL images (embedded + not), violations counts only the non-embedded ones.

audit_find_single_pin_netsA

Find nets that have EXACTLY ONE pin AND at least one net-label / port / power-object on them.

A single-pin net is normal for unused / no-connect pins. A single-pin net with a named label is a broken connection: the designer asserted "this signal goes somewhere" by naming it but the other end never made it onto the schematic. Common causes are a typo in a net-label that doesn't match its peer, a sheet-entry vs port-name mismatch on a hierarchical block, or an off-page connector that was deleted while routing was being re-arranged.

The flat hierarchy is queried (DM_DocumentFlattened) so nets spanning multiple sheets via off-page connectors are counted as one net.

Returns: Dict with {checked, violations, items[]}. Each item carries {net, designator, pin} for the lonely pin.

audit_find_mpn_inconsistenciesA

Find groups of ICs with the same (lib_ref, comment) but DIFFERENT Manufacturer Part Numbers.

Two presumably-identical parts pointing at different MPNs is almost always a bug: either a typo / accidental override during a sub-circuit clone, or means the design genuinely wants two sources but lost the alternates table somewhere. The agent reviewing the BOM should see this before purchase.

Pattern: ports the dashboard's analyzeMpnConsistency JS heuristic to an authoritative Pascal-side audit.

Returns: Dict with {checked, violations, items[]}. Each item carries {lib_ref, comment, mpns, designators} where mpns is a comma-joined list of the conflicting part numbers and designators lists the components involved.

audit_find_missing_datasheetsA

Find ICs (designator U*) with no fetchable datasheet URL in any of their parameters.

Checks the standard Altium slots:

  • HelpURL, Datasheet, DatasheetURL

  • ComponentLink1URL..ComponentLink4URL

At least one of those needs to carry an http(s):// value for the IC to count as covered. The agent's review discipline requires fetching the actual manufacturer datasheet before making any device-related claim: ICs without a stored URL force the agent to web-search the part name, which is slower and more error-prone than a direct link.

Pattern: ports the dashboard's pickDatasheetLinks heuristic to an authoritative Pascal-side audit.

Returns: Dict with {checked, violations, items[]} where each item carries {designator, comment, lib_ref} for an IC missing any datasheet link.

audit_find_pin_net_name_mismatchesA

Find IC pins whose NAME looks like a power / ground pin but whose connected NET is not actually a power / ground rail.

Examples flagged:

  • pin "VCC" wired to a non-power net (swapped rail bug)

  • pin "GND" or "VSS" wired to a non-ground net (broken reference plane) ERC won't catch these: the wire is connected, the names just disagree on intent. A class of bug that ships when the user copied a sub-circuit and forgot to re-tag the rail.

Pattern: ports the dashboard's JS-side analyzePinNetMismatch as an authoritative MCP audit.

Returns: Dict with {checked, violations, items[]} where each item carries {designator, mismatches[]} with one entry per offending pin: {pin, name, net, kind}. kind is power_pin_on_non_power_net or ground_pin_on_non_ground_net.

audit_find_missing_decouplingA

Find ICs whose power pins lack a nearby decoupling cap.

For every IC, identify its power pins (by net name or by pin name pattern). For each power pin, check if any capacitor (designator C*) shares the same net. Buckets:

  • missing: no power pin has any cap on its net (worst)

  • partial: some power pins covered, some not

ICs with full coverage are not surfaced (no problem to fix). ICs with NO recognised power pins are skipped (we can't distinguish "no power needed" from "couldn't detect").

Catches the single most-skipped pre-release review check: a missing local bypass doesn't fail ERC, doesn't fail DRC, but bites at first power-on with brown-outs / random resets.

Pattern: ports the dashboard's JS-side analyzeDecoupling.

Returns: Dict with {checked, violations, items[]} where each item is {designator, status, covered_pin_count, uncovered_pins[]}. status is missing or partial. uncovered_pins[] lists each {pin, name, net} that needs a cap.

audit_find_unconnected_ic_pinsA

Find IC pins with an empty / unset net (excluding pins intentionally named NC / DNC / RSVD / RESERVED).

Walks the project BOM (cheap: uses the already-cached project.get_bom snapshot, no extra Altium round-trip) and, for every component whose designator starts with U, counts pins whose net is empty / null / "?". Pins whose NAME marks them as no-connect by convention are skipped because those aren't bugs.

Same heuristic the Components tab's "unconnected IC pins" chip runs in JS, but now exposed as a first-class MCP tool so the agent can drill in from one call and the result is consistent between the dashboard and the lint sweep.

Returns: Dict with: - checked: ICs inspected - violations: ICs with at least one unconnected pin (the COUNT here is per-IC, not per-pin) - unconnected_pin_total: sum of unconnected pins across all flagged ICs - items: per-IC {designator, unconnected_pins[]} listing pin numbers + names that were flagged.

audit_component_param_visibilityA

Check that every placed component shows the parameters its class requires for a legible schematic and a procurable BOM.

Per-class rules:

  • Capacitor (designator C*): must show a value ending in F (e.g. 10uF) AND a voltage rating ending in V (e.g. 16V). Missing voltage = catastrophic failure risk on transient overvoltage.

  • Resistor (R*): must show a value ending in R, k, M, or m. Missing value = BOM ambiguity, can't order.

  • Inductor (L*): must show inductance ending in H (e.g. 10uH) AND saturation current ending in A (e.g. 2A). Missing saturation current = core-saturation failure on power-supply inductors.

  • IC (U*): must show a visible parameter whose name starts with Manufacturer Part Number. Missing MPN = unprocurable and unverifiable footprint.

The check looks at component parameter slots and the on-canvas Comment field; either is acceptable. Hidden values (IsHidden=True) do NOT satisfy the rule because they're invisible during review.

Operates on the compiled / flattened doc tree (same as the BOM) so multichannel and hierarchical designs are covered.

Returns: Dict with: - checked: how many components matched a known class - violations: how many of those have at least one missing required value - items: list of {designator, class, missing[]} violation entries. missing enumerates which required fields are absent (capacitance, voltage, resistance, inductance, saturation_current, mpn).

audit_power_port_orientationA

Check that schematic power-symbol orientations follow the standard convention so reviewers' eyes can scan the sheet quickly.

Rules:

  • Ground symbols (ePowerGndPower, ePowerGndSignal, ePowerGndEarth) MUST face down (Orientation = 270deg).

  • Power bars (ePowerBar, used for VCC/VDD/V+ rails) MUST face up (Orientation = 90deg).

Stylistic but high signal: a flipped ground symbol or a sideways rail is invariably an editing accident, and one violation can hide a real wiring issue under it because the eye keeps wanting to fix the orientation first.

Returns: Dict with: - checked: total power-symbols inspected (grounds + bars) - violations: count with wrong orientation - items: list of {text, style, expected, actual, sheet}

audit_tented_via_ratioA

Count tented vs untented via SURFACES on the active board and report the tented ratio.

Untented vias expose the via barrel through solder mask; in humid or salt-spray environments that risks acid traps and copper corrosion. For consumer boards spec is usually "all vias tented top + bottom"; for prototyping boards a lower ratio is fine.

Counts top and bottom surfaces separately (a via reaching only the top layer contributes one surface, a through-via two). Buried / blind via surfaces that don't reach an outer layer are ignored.

Returns: Dict with: - total_surfaces: tented + untented (denominator) - tented: surfaces with tenting on - untented: surfaces with tenting off - ratio: tented / total (0.0 - 1.0; 1.0 = all tented) - violation_pct: untented / total * 100

audit_find_floating_portsA

Find schematic ports that aren't actually connected to anything.

On each schematic sheet a port is "floating" if its net does not appear on any component pin OR any sheet-symbol entry on the same sheet. A floating port is usually an editing leftover (renamed a net but forgot to retag the port) or a never-completed hookup -- ERC can miss these when the port carries a valid net-label tag that just doesn't connect to anything physically.

Walks the compiled / flattened doc tree so multichannel / hierarchical designs are covered.

Returns: Dict with: - checked: total port objects inspected - violations: floating-port count - items: per-violation {net, sheet, location}

audit_find_bad_connectionsA

Find PCB tracks / arcs with endpoints that are close to another same-net primitive but don't actually touch.

Walks every track / arc on a signal layer. For each endpoint, looks for ANOTHER same-net primitive (track / arc / pad / via) within tolerance_mils. If nothing same-net is in range, the endpoint is "dangling": Altium may show it as connected in the analyser (centre-point heuristic) but the photoplot will not bridge it, producing a real open on the fab side.

Visual review misses these because at 100% zoom the gap is invisible; DRC sometimes catches them under a "Clearance" or "Modified Polygon" rule but not reliably.

Skipped: teardrops (intentionally offset), primitives inside components (they ride the pad), primitives without a net, full circle arcs (no anchor endpoint).

Args: tolerance_mils: Coord tolerance in mils. 0 = exact match required (rare, since router can place tracks on sub-mil grid). Defaults to 1 mil; bump to 5 or 10 to surface gross misalignments only.

Returns: Dict with: - checked: track + arc primitives inspected - violations: primitives with at least one dangling endpoint - tolerance_mils: echo of the tolerance used - items: per-violation {kind, layer, net, at} where at is the dangling endpoint's "(x,y)" in mils

audit_find_signal_vias_without_returnA

Find signal vias that have no nearby ground / power via for return-current flow.

For every via NOT on a ground/power net (heuristic: net name starts with GND / V / + / - or contains "GND"), look for at least one ground/power via within radius_mils. If none is found, the signal via likely lacks a return-current path -- when a high-speed signal changes layers, the return current needs to cross the dielectric somewhere, and a nearby ground via shortens that path.

Tradeoff: this is a SIMPLIFIED proximity heuristic, not a stackup- aware analyser. The full check (reference-layer tagging, stripline vs microstrip rules, paired-reference handling) is a much larger GUI-driven analysis. For first-pass review, proximity flags the obvious cases.

Args: radius_mils: How close a return via must be. Default 50 mils (~1.3mm) -- typical "good practice" is within 1-2 mm of the signal via.

Returns: Dict with: - checked: signal vias inspected (non-power/ground) - violations: signal vias with no return via in range - radius_mils: echo of radius used - items: per-violation {net, at} where at is "(x,y)" mils of the offending signal via

audit_find_pads_near_board_edgeA

Find PCB pads / vias closer than clearance_mils to the board outline (depaneling damage hazard).

Copper too close to the routed edge gets scuffed / scored / torn during v-cut + mouse-bite break. IPC-2221 and typical fab houses want at least 20-50mil clearance for critical components, more for connectors. Worth tightening to 50+ for boards going to depaneling rather than rounded-corner manufacture.

The gap is measured from the outline's own segments, lines and arcs, to the pad's copper: a via or round pad by its radius, a rectangle by its nearest corner or edge midpoint, at whatever rotation. Round and other curved outlines are covered.

Args: clearance_mils: Minimum gap to flag as a violation (default 25). Try 50 if your fab depanels by v-cut.

Returns: Dict with: - checked: total pads + vias inspected - violations: how many are within the clearance - unmeasured: pads that could not be measured, which are therefore neither passed nor flagged - clearance_mils: echo of the threshold used - items: per-violation {kind, designator, distance_mils, at} where at is "(x,y)" mils.

audit_find_components_outside_board_outlineA

Find PCB components whose origin sits outside the board outline.

Editing accident catch: the user grabbed a component and moved it off the PCB by mistake. Usually invisible during normal review because the component is off-canvas; DRC doesn't catch it (DRC is between primitives, not "is this object on the board").

Uses IPCB_BoardOutline.PrimitiveInsidePoly for the actual polygon-inside test, so non-rectangular boards are handled correctly.

Pattern: brett's SelectCMPInOutSideBOL.pas.

Returns: Dict with: - checked: total components inspected - violations: components outside the outline - items: per-violation {designator, layer, at} with at = "(x,y)" mils.

audit_find_off_grid_componentsA

Find schematic components placed off the snap grid.

Schematic best practice: components sit on a 100-mil (or whatever the project standard is) grid so pins land predictably on wire endpoints. Off-grid placement is the classic "I edited around it and never resnapped" trap: the symbol LOOKS wired but the pin doesn't actually touch the wire, ERC silently passes (no visible net) and the connection is missing on the netlist.

Args: grid_mils: Grid size to check against (default 100). Use 50 if your sheets are set to a half-step grid.

Returns: Dict with: - checked: total placed components inspected - violations: off-grid component count - grid_mils: echo of the grid used - items: per-violation {designator, sheet, at, dx, dy} where dx/dy are the off-grid magnitudes in mils. Snap to -dx / -dy (or whichever direction is closer) to restore grid alignment.

audit_find_removed_pad_shapesA

Find pads / vias whose copper annular ring has been removed on layers where it's still needed.

Altium's "Tools → Remove Unused Pad Shapes" optimization deletes per-layer pad copper where a primitive isn't electrically used on that layer. Applied too aggressively, it leaves a via that drills through an inner layer with NO annular ring there: any later trace edit on that inner layer that tries to land on the via will silently fail to connect at fab.

Detection:

  • Vias: Via.SizeOnLayer(L) <= Via.HoleSize means no copper left, just the drilled hole.

  • Pads: Pad.IsPadRemoved(L) is True.

Pattern: brett's PadShapeRemoved.pas (read-only; we surface coordinates rather than highlight on canvas).

Returns: Dict with: - checked: total pads + vias inspected - violations: how many layer-instances are flagged (one via removed on 3 layers counts as 3) - items: per-violation {kind, designator, net, layer, at} where at is "(x,y)" mils.

audit_find_designator_collisionsA

Find schematic components that share a designator across sheets.

Annotation should make every designator unique across the project, but paste-from-another-sheet operations and post-annotate edits can leave duplicates that the agent / reviewer want to surface before they hit ERC.

Walks source schematic sheets (not compiled docs: compiled flattening would auto-disambiguate multichannel instances with channel suffixes, hiding the real source-level collision).

Returns: Dict with: - checked: total components inspected - violations: count of distinct designators that occur more than once - items: per-collision {designator, count, sheets} where sheets is a comma-joined sheet-filename list

audit_find_via_antennasA

Find vias connected on only one layer (resonating stubs).

At RF / high data rates the unused side of a one-layer-connected via acts as an open-ended transmission-line stub: it resonates at frequencies whose quarter-wavelength matches the stub length, reflecting signal back into the trace and degrading rise time + EMC compliance. Common after iterative editing where a layer-change via gets stranded when the destination trace was later rerouted on a different layer.

Counts a via "connected" on a layer when:

  • it's a signal layer with a track/arc/pad/fill/region touching the via (Board.PrimPrimDistance = 0), OR

  • it's a plane layer and Via.IsConnectedToPlane returns True.

Returns a JSON list (rather than selecting and highlighting).

Cost: O(vias × layers × spatial-prims-near-via). Typical boards finish in 1-3 seconds; very dense boards may take longer.

Returns: Dict with: - checked: total vias inspected - violations: antenna-via count - items: per-violation {net, at, connected_layers} where at is "(x,y)" mils and connected_layers will always be 1.

audit_find_unmatched_portsA

Find schematic nets with port-direction mismatches across sheets.

Catches the two cases that matter most:

  • multi_output: a net has MORE THAN ONE Output port. Two drivers fighting for the same net: almost always a wiring error or copy-paste mistake.

  • no_driver: a net has Input ports but ZERO Output (or Bidirectional) ports anywhere in the project. Orphan signal: something is listening but nothing is talking.

Walks compiled docs so multichannel designs are covered. Ports are grouped by their flattened net name, so net-label renames keep ports correctly clustered.

Returns: Dict with: - checked: total port objects inspected - violations: net count with at least one issue - items: per-net {net, issue, output_count, input_count}. issue is multi_output or no_driver.

audit_variant_not_fittedA

List components marked Not Fitted in the project's CURRENT variant.

Manufacturing context: a Not-Fitted component is on the BOM as a placeholder but should NOT receive paste on the stencil, otherwise the SMT line deposits paste on empty pads and bridging forms on rework. Many houses also want a "DNP" silkscreen marker on the artwork. This tool returns the list of components the agent should consider for those treatments.

This is the identify half. pcb_apply_dnp_paste_exclusion is the remediation half: it takes this list (or re-derives it) and collapses the stencil aperture on each Not-Fitted component's surface pads. Kept as two calls so the detection can be reviewed before anything on the board is edited.

Returns: Dict with: - variant: the current variant's description (empty when the project has no variant selected: DNP only applies to a variant) - checked: total flattened-project components - violations: how many are NotFitted in this variant - items: per-violation {designator, comment, unique_id}

audit_find_invalid_regionsA

Find PCB polygon regions whose area is zero or unset.

These are leftover from cancelled polygon-pour operations or corrupted file imports. They don't render visibly but can throw DRC violations under "Modified Polygon" rules and bloat board size. Read-only: this tool only surfaces them. Auto-delete belongs in a separate user-confirmed mutating flow because misclassification could remove a real (legitimately small) region.

Returns: Dict with: - checked: regions inspected (skips primitives owned by components or dimensions) - violations: invalid-region count - items: per-violation {layer, at}

pcb_autorouteA

Route the focused board with the in-house layout engine, as a job.

Reads the board exactly (pcb.get_layout_model: padstacks per layer, rules, pours, planes, in Altium's internal units), then routes it in the background and returns a job id at once. Poll design_job_status; design_job_result gives the tracks and vias to add, the exact DRC's verdict on the routed board, and which pours it relied on as planes. Nothing is written to the board until pcb_autoroute_apply.

This is the in-house engine: no Altium autorouter, no external program. Inner layers a pour covers are used as planes and not routed on; a redundant one is given back to routing when the board cannot be completed without it.

With the stage switches on, the routing runs in planned stages, each one's copper fixed for the next: differential pairs (coupled at their gap, skew evened with bumps), a dog-bone via for every pad a plane or pour joins (never a via in a passive's pad; an exposed pad gets a via array), access lanes kept straight out of fine-pitch IC pins, buses laid as nested lanes, then everything else negotiated, high-current nets first, then rails, then the rest. Every bend is 45 degrees or less, and no via goes into a passive's pad. Over 160 finished boards these defaults routed 98.93% mean against 98.85% for the router before the stages (two boards fewer at 100%, all DRC-clean, about 29% more time); plane fanout is off by default because it cost completion. With all four switches off this is the router before the stages, exactly.

Args: expect_file: full path of the board to route. The read refuses on the first reply if the focused board is another one. nets: route only these nets; all other copper stays as it is. None routes every net with two or more pads. use_planes: treat inner layers a single pour covers as planes (default True). diff_pairs: route the board's differential pairs (its own definitions, else read from _P/_N, +/-, DP/DM net names) as coupled pairs first (default True). plane_fanout: give every surface pad of a plane or pour net its dog-bone via before the signals (default False). pin_lanes: keep the first 0.8 mm straight out of each IC pin at 0.65 mm pitch or finer for that pin's own net (default True). bus_lanes: lay three or more nets between the same two parts as one group of nested lanes (default True).

Returns: {"job_id", "board", "read": {pads, components, nets, layers}}, or {"error": ...} when the read failed or found another board. The job's result adds, beside the copper and the DRC verdict: via_in_pad (every via in a pad, for the fab notes: filled and capped), pairs (per pair: lengths, skew, bumps, layer changes, or why it was left to the router), buses (per bus: its lanes, what was ripped up and planned again, or why not), and unreached (every pad left unrouted, with its place and the nets in its way).

pcb_autoroute_applyA

Write a finished pcb_autoroute job's tracks and vias to the board.

Refuses unless the focused board is expect_file. With checkpoint (default), first snapshots the board's folder as it is ON DISK, so it can be restored with app_restore_checkpoint. Nothing is saved first: unsaved edits in the editor are not in the checkpoint, so save the board yourself beforehand if it has any. Coordinates, widths and via sizes keep their decimals.

Args: job_id: a layout_route job that has finished. expect_file: full path of the board the job routed. checkpoint: take a checkpoint first (default True). repour: repour the board's polygons afterwards when the job relied on them, as planes or as pours joining a net, or routed through their old poured copper (default True). A poured net's connections are not there, and the new copper is not cleared, until the polygons are repoured round it.

Returns: {"tracks": {placed, failed}, "vias": {placed, failed}, "checkpoint": ..., "repoured": bool, "via_in_pad": [...], "notes": [...]} or {"error": ...}. via_in_pad lists the vias written into pads (BGA balls, exposed pads), which the fabrication notes must ask to be filled and capped.

pcb_autoplaceA

Place the focused board's parts with the in-house placer, as a job.

Reads the board exactly (pcb.get_layout_model), then places it in the background and returns a job id at once. Poll design_job_status; design_job_result gives the moves, the wirelength before and after, any overlaps, and the exact DRC's verdict on the placed board (unrouted). Nothing is moved on the board until pcb_autoplace_apply.

Two strategies. "blocks" plans the board by sub-circuit first: each IC with its decoupling, pull-ups and filters in columns round it, connectors with the parts on their lines, passives in rows, repeated channels as identical tiles in a grid, the blocks placed in signal-flow order from the connectors; its result also lists the blocks (name, rectangle in mils, members) and every part the legaliser had to move, with the moves over 80 mil flagged as floorplan problems. "analytic" places by wirelength alone (quadratic placement, spreading, legalising, refinement).

Parts keep their side. Locked parts, and parts whose designator starts like a connector, mounting hole, fiducial, test point, switch or battery, stay where they are unless named in parts: they sit where they do for mechanical reasons. The board's own routing does not move with its parts: place before routing, or take the routing up first with pcb_unroute.

This is the in-house engine: no Altium auto-placer, no external program.

Args: expect_file: full path of the board to place. The read refuses on the first reply if the focused board is another one. parts: designators to move; every other part stays. None moves every part that is not locked or mechanical (see above). compact: spread the parts over only as much room as they need, round where their connections pull them, instead of over the whole board. For a small circuit on a large board; over the benchmark boards it routes slightly worse, so it is off by default. Analytic strategy only. strategy: "blocks" or "analytic"; None takes the placer's default, "blocks". A board where the floorplan has to push more than a quarter of its parts out of place is placed by "analytic" instead, and the result says so under fallback.

Returns: {"job_id", "board", "strategy", "read": {...}}, or {"error": ...} when the strategy is unknown, or the read failed or found another board.

pcb_autoplace_applyA

Move the parts of a finished pcb_autoplace job on the board.

Refuses unless the focused board is expect_file. With checkpoint (default), first snapshots the board's folder as it is ON DISK, so it can be restored with app_restore_checkpoint; unsaved edits in the editor are not in it, so save first if there are any. Positions are whole mils, as the job reported and judged them.

Args: job_id: a layout_place job that has finished. expect_file: full path of the board the job placed. checkpoint: take a checkpoint first (default True).

Returns: {"moved", "failed", "expected", "checkpoint", "notes"} or {"error": ...}.

route_planA

Route the board offline (grid A*) and return placeable ops.

Pure Python -- no Altium round-trip unless fetch_geometry is set. Output tracks are {x1, y1, x2, y2, width, layer, net_name} (the pcb_place_tracks item shape) and vias are {x, y, net, size, hole_size} (the pcb_place_via params), integer mils, so the result applies verbatim. Per-net failure is honest data (status failed), not a tool error.

Live sequence: fetch geometry -> this tool -> pcb_place_tracks / pcb_place_via -> pcb_run_drc -> route_plan_repairs -> apply -> repeat.

Args: geometry: Gen_GetPcbGeometry payload (bbox / outline / pads / tracks / vias, all mils). Pads and copper of nets NOT being routed stay in the obstacle map. rules: Routing rules, all mils -- clearance_mils, track_width_mils (int, or per-class dict with "default"), via_size_mils, via_drill_mils, layers (default TopLayer + BottomLayer). None uses defaults. nets: Route only these net names; everything else stays a static obstacle. Unknown names are reported in unknown_nets. None routes every netted pad group. net_classes: Net name -> class (power / ground / differential / ...). Sets routing order and the per-class track width. Unlisted nets are signal. grid_pitch_mils: Routing grid pitch in mils (default 25). bend_penalty: A* corner cost in grid-pitch units. via_cost: A* layer-change cost in grid-pitch units. max_expansions: Per-connection A* budget so a walled-in net fails fast. fetch_geometry: When True and geometry is None, pull the live board over the bridge.

Returns: {"ok": True, "summary": {nets_total, routed, failed, skipped, completion, track_count, via_count, total_length_mils}, "order": [...], "nets": {net: {status, class, width, tracks, vias, ...}}, "tracks": [...], "vias": [...], "validation": {...}}; with a nets filter also requested_nets / unknown_nets. {"ok": False, "reason": ...} on malformed input.

``geometry`` says which board state was planned against:
``source`` is ``live`` when this call read the board and
``caller`` when you supplied the dict, plus the ``bbox`` and
``counts`` from that payload. A ``caller`` plan is only as
current as the snapshot behind it, and this tool cannot date
it: after placing anything, re-read before re-planning or
the next plan routes through copper it cannot see.
route_plan_repairsA

Turn a DRC violation payload into an ordered repair plan.

Pure Python, stateless. Classifies the pcb_run_drc payload (or a bare violation list) into buckets (net_clearance / pad_clearance / unrouted / antenna / width / other), then plans actions: rip_and_reroute (worst clearance offender first), nudge {net, dx, dy, x_mils, y_mils} for a lone pad-clearance conflict, widen/narrow {net} for width violations, escalate {reason} when the plan cannot converge alone. Deltas/coordinates are integer mils.

Executor contract: apply the actions in order (rip_and_reroute = pcb_delete_net + route that net again via route_plan with the nets filter or a DSN round-trip; nudge = obj_modify on the primitive nearest (x_mils, y_mils); widen/narrow = pcb_set_track_width; escalate = stop and surface the reason). Then pcb_run_drc again and re-plan from the fresh violations -- the loop's outer iteration bound is the caller's.

Args: violations: pcb_run_drc result {violation_count, violations} or a bare list of violation dicts. max_rounds: Rip budget for the clearance worst-offender loop, integer >= 0 (0 = escalate-only for clearance).

Returns: {"ok": True, "actions": [...], "rounds_used": n, "ripped_nets": [...], "counts": {bucket: n}}; {"ok": False, "reason": ...} on malformed input.

review_designA

EDA-agnostic design review of the open board.

Runs the same netlist-level review regardless of the active EDA (Altium or KiCad): board stats and net classes, annotation hygiene (unannotated, duplicate references, missing values), and connectivity (single-pin nets, shorted pins/components, unconnected parts and pads, missing decoupling). backend ("altium" | "kicad") targets one tool when the server runs in "both" mode; omit it to use the active backend.

Geometric DRC (clearances, courtyards) is separate and backend-specific.

get_board_infoB

Object counts and net-class summary for the open board, from the active backend (or the one named by backend).

list_componentsB

Every component on the open board (reference, value, footprint, kind), from the active backend (or the one named by backend).

run_drcA

Run geometric design-rule check (DRC) on the open board.

Dispatches to the active backend: Altium runs its native DRC; KiCad runs kicad-cli pcb drc (KiCad's own CLI) on the board file on disk, so save pending edits first for current results. Returns violation counts and a normalized list. backend targets one tool in "both" mode.

run_ercA

Run the schematic Electrical Rules Check (ERC) on the open design.

Dispatches to the active backend: Altium runs its native ERC; KiCad runs kicad-cli sch erc on the schematic file on disk (save pending edits first). Returns violation counts and a normalized list. backend targets one tool in "both" mode.

list_netsB

Every net on the open board with pin count and class (power / ground / signal), from the active backend (or the one named by backend).

tool_catalogA

Discover tools by category / maturity / interaction without loading every schema.

The surface has 350+ tools; loading them all strains a client's context. This meta-tool returns a filtered, classified index so a client can find the right tool first, then rely on its full schema.

Filters (all optional, AND-combined): category: core, application, project, library, generic, schematic, pcb, audit, design, simulation, routing, meta, parts (and kicad on the KiCad backend; the EasyEDA tools file under the same subject headings as Altium's, so "pcb" means the same thing on either). "core" holds the EDA-agnostic main flow (review_design, get_board_info, list_components, list_nets, run_drc, run_erc) and is the usual starting point. Call with NO filter to get the live categories map rather than trusting this list. maturity: how far a tool can be exercised without Altium. offline needs no Altium at all; simulator is bridge-backed but every command it sends is implemented by the in-repo Altium simulator; live_only sends at least one command only real Altium answers. Filter on offline to find what runs with nothing open. interaction: readonly | silent | modal | partial. modal tools pop a blocking Altium dialog; partial ones leave the job incomplete: plan around both. query: case-insensitive substring over the tool name (and description when with_description). with_description: include the one-line summary per tool. with_schema: include each tool's parameters and required list. ESSENTIAL under the minimal toolset: no tool schema is loaded up front there, so calling tool_invoke without this means guessing argument names. Filter first, schemas are dropped past a cap so this cannot flood the very context the minimal toolset exists to protect.

Returns {count, tools:[{name, category, maturity, interaction [, description][, parameters, required]}], categories:{cat: n}}.

tool_invokeA

Invoke any registered tool by name: the companion to tool_catalog.

A client with a limited context can expose only the core tools plus this pair: discover a tool with tool_catalog, then run it here by name without ever loading its schema. arguments is the tool's keyword-argument dict.

GET THE ARGUMENT NAMES FIRST. Under the minimal toolset no schema is loaded up front, so call tool_catalog(query="<name>", with_schema=True) and use the parameters/required it returns. Guessing is a real failure mode: several tools take names that look obvious but are not (current_amps, not current_a), and the same tool can differ between backends.

Returns the tool's own result (JSON-decoded), or {"error": ...} for an unknown/disallowed name. Note: bypassing the schema means argument mistakes surface as the target tool's own error, not a pre-validation message.

tool_guideA

How to do something correctly, and what is genuinely impossible.

USE THIS BEFORE CONCLUDING A CAPABILITY IS MISSING. The recorded failures are not "could not find the tool name", which tool_catalog already solves. They are aiming a board tool at a library, and reporting a capability absent when it was present. Both return a confident wrong answer rather than an error.

Answers three distinct things, kept apart on purpose: the tool and its prerequisites, the tool you were probably reaching for and why it acts on another document, and the short list of things that are proven impossible with the reason.

An empty result means this guide has nothing on the subject. It is not evidence that the server cannot do it: fall back to tool_catalog.

Search here for help when a tool seems missing, when you are not sure which tool to use, when an operation seems not supported, or when a call failed because the wrong document was open. (tool_catalog matches on this description, and searching the obvious word, help, used to return four other tools and not this one. The vocabulary of being stuck has to appear here, not only the vocabulary of the answer.)

Args: task: What you are trying to do, in your own words, e.g. "delete silkscreen from a footprint". Matched on keywords, so a rough phrase is fine. document_kind: Narrow to one of library, board, schematic, project, any. Optional. backend: altium, kicad or easyeda. Optional; omit to see every backend's answer.

Returns: Dict with recipes (each with use, avoid and a note), not_possible, and matched.

part_searchA

Search EVERY enabled part provider and merge the results.

No provider is a default and none is preferred: all are queried equally, hits carry the provider that found them, and the merged order is alphabetical by provider then part. That ordering is NOT relevance, so do not read the first hit as the best one.

Each provider reports its own status. A provider that cannot answer says why (endpoint withdrawn, software missing) instead of returning nothing, because "unavailable" and "no such part" are different answers and only one means stop looking.

Call with an empty query to list the providers and what they are, including who operates them.

TWO KINDS OF SOURCE, merged but never conflated. A library provider yields GEOMETRY: a symbol or footprint you can place. A catalogue provider (the distributors and aggregators) yields IDENTITY: the manufacturer part number, the datasheet, what is in stock, and NO geometry at all. Every hit carries its kind, because finding out that a distributor hit has no symbol after choosing the part is the expensive way to learn it.

The two answer different questions rather than ranking against each other: a catalogue tells you WHICH part to use and hands you the datasheet every check here measures against, and a library tells you whether you can draw it.

NOTE on maturity="offline": in this catalog that means "needs no Altium", which is true here. It does NOT mean "needs nothing": the remote providers want the internet. With no network the search still succeeds on whatever local providers are enabled (kicad_local reads libraries already on disk) and reports the remote ones as unavailable, so a degraded answer is still a useful one.

DATASHEET DISCIPLINE: a hit is a lead, not a verified part. The provenance and license fields say what the provider claims about where its geometry came from, and a blank means unknown, not permissive. Audit any imported footprint against the manufacturer land pattern with lib_audit_footprint_vs_datasheet before trusting it.

Args: query: substring matched against part name, manufacturer and keywords. Empty lists the providers instead. limit_per_provider: cap per source, so one large registry cannot crowd out the others.

Returns: {"ok", "count", "providers": {name: status}, "hits": [...], "by_mpn": [...]} or, for an empty query, {"ok", "providers": [...]}.

``by_mpn`` correlates the same part across sources, so you
can see which providers carry it and what each states about
provenance and license. Cosmetic spelling differences fold
together; WILDCARD part numbers do not, because KiCad's
``...C8Tx`` is a family placeholder rather than a spelling
of ``...C8T6``, and merging them would assert an equivalence
that is not safe to assume.
part_fetchA

Fetch one part's detail, by the ref a search returned.

Pass the ref straight through: it is provider:part_id, so the source travels with the id and the merged results behave like one catalogue. part_search puts a ref on every hit.

The source is still explicit, never inferred. Nothing here picks a provider for you: a bare id with no provider and no recognised prefix is refused rather than guessed at, because guessing is how one source quietly becomes the answer to everything. provider may still be given separately, and wins over a prefix if both are present.

Set download_dir to also write the provider's library files there, when it offers any. Off by default because it writes to disk, and a fetch should not do that unasked.

Downloaded files are checked against the KiCad installed on this machine: a registry may publish a NEWER s-expression format than the local KiCad can open (observed live, format 20260206 against KiCad 10.0.1's 20251024, where the symbol parser refuses the file outright). Any such file comes back with a *_warning entry rather than looking like a clean download.

Returns the provider's own detail plus a normalised summary (mpn, manufacturer, datasheet, provenance, license) where the provider supplies one, and files when a download was asked for.

design_live_viewA

Open the live layout view and return its URL.

Makes sure the local dashboard is running (it usually is, started with the MCP server) and points at its Layout tab, which draws the board as the in-house placer and router change it: parts, pads, tracks per layer, vias, pours, blocks and planned lanes, with new and moved items glowing for 30 s and removed copper fading out. Next to it is the decision log, newest first, and a status line (routed n / N nets, k partly). Give the URL to the user so they can watch while you work. Reads files only; no bridge call.

Args: open_browser: also open the URL in the default browser.

Returns: {"url", "dashboard": "running"|"started"|"disabled"|"failed", "version", "board", "status"}, plus "reason" when the dashboard is not up. version is 0 until a board has been published.

design_live_noteA

Log a decision to the live view, before acting on it.

The Layout tab lists decisions newest first next to the board, so the user sees what is about to happen and why, and can stop it. Call this BEFORE the step it describes, one decision per call, in a sentence or two: what you will do and the reason (the datasheet figure, the rule, the measurement).

Args: section: what the decision is about, a short label such as placement, routing, rules or a block's name. text: the decision and its reason.

Returns: The logged entry {"seq", "time", "ts", "section", "text"}, or {"error": ...} when text is empty.

design_live_snapshotA

Publish a board to the live view as its next version.

The placement and routing jobs publish on their own, and their apply tools republish what they wrote. Use this for any other board state: the board as it is in the EDA now (expect_file), a board saved as LayoutBoard JSON (LayoutBoard.save, .json or .json.gz), or the last board again with a note. The Layout tab highlights what changed since the version before.

Only expect_file calls the bridge: it reads the focused board exactly (pcb.get_layout_model), a few seconds on a large board. It is never done on its own, because reading some boards has crashed Altium's scripting engine.

Args: note: what this snapshot shows; also logged as a decision. board_json_path: a LayoutBoard JSON file. expect_file: full path of the focused board, to read it from Altium as it is now. The read refuses if another board is focused. (Neither: republish the board the last engine job, apply or snapshot in this server published.)

Returns: {"version", "board", "status": {nets, routed, partly, not_started}, "changes": {added, moved, removed}, "seconds", "url"}, or {"error": ...}.

datasheet_fetchA

Download a datasheet PDF into the workspace and return its path.

Read the returned path yourself (the pages with the package drawing and the recommended land pattern); nothing here parses the PDF. The file is kept under <workspace>/datasheets, named by a hash of the URL, with a JSON sidecar recording the URL, part number, SHA-256, size and fetch time, so a second call for the same URL is answered from the cache (cache: "hit") with the same bytes. A cached copy that no longer matches its recorded hash is fetched again.

Refused, with nothing stored: a URL that is not http or https; a file over 50 MB; a server that does not answer within 60 s; and a reply that is not a PDF (no %PDF header), which is what a login or cookie page looks like. For those, download the file in a browser and read the local copy.

Args: url: the datasheet's http or https URL, ideally the manufacturer's own. part_number: recorded in the sidecar, for the citation.

Returns: {ok, path, sidecar, cache, url, final_url, sha256, size, pages, pages_approximate, pages_method, fetched_at}. pages is counted from the page objects in the file without a PDF parser and can be None or off for a compressed file.

footprint_check_packageA

Check a package description for numbers that cannot be right.

Run this on every package transcribed from a datasheet, before drawing a footprint from it. Offline.

Sanity checks (each finding names the dimensions, their values and citations, and why the combination is impossible or unusual): limits in order; lead width below the pitch; each row of pins fits the body it comes out of; lead span larger than the body for leaded parts; the foot shorter than the lead's reach and two terminals fitting the span; the exposed pad inside the body and clear of the terminals; ball diameter below the pitch; values 25.4 times off their neighbours (mils typed as mm); pin counts the family can have; the vendor land pattern consistent with itself. error is impossible, warning unusual.

Cross-check, when the package carries the datasheet's recommended land pattern as vendor: the IPC pattern computed from the package against it, per quantity (pitch, span, land length and width, tab, exposed pad, grid land), with both numbers, both citations and the difference. Beyond the tolerance measured between real vendor patterns and IPC, or a factor of two apart, the pair is reported as a likely transcription error in one of the two sources.

Args: package: the PackageSpec as an object or JSON text, the same schema lib_create_footprint_from_package takes.

Returns: {ok, errors, warnings, findings, cross_check, examined, notes}. ok is true when no sanity finding is an error and the cross-check (if it ran) agreed.

footprint_render_packageA

Draw the land pattern a package gives, to compare with its datasheet.

Offline. Renders the footprint the engine would build (lands with their numbers, paste windows, silkscreen with the pin 1 dot, assembly outline, courtyard, origin and a 1 mm scale bar) and draws the package's terminals over the lands as dashed green outlines, so a land that misses its terminal shows. A caption gives the pattern's pitch, span and land sizes. Read the returned image next to the datasheet's package drawing and recommended land pattern.

Args: package: the PackageSpec as an object or JSON text. density: L, N or M (least, nominal, most). path: where to write the SVG; default <workspace>/renders/<name>_<density>.svg. png: also write a PNG beside it through the headless browser the board renders use, when one is installed.

Returns: {ok, svg_path, png_path, name, density, metrics, warnings}, with rasterize_note when no PNG could be made.

Prompts

Interactive templates invoked by user choice

NameDescription
autonomous_designDrive a full spec-to-board design run with the harness.

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

B3.3/5.0

Scored across 480 tools

Disambiguation2/5

The 480-tool set contains many near-overlapping tools: app_run_menu vs app_click_menu vs app_run_ui_command; app_list_open_dialogs vs app_read_window vs app_ui_snapshot; proj_get_nets vs proj_get_connectivity vs proj_get_component_info; and many audit_find_* and pcb_get_* variants. Descriptions often explain distinctions, but the sheer breadth makes misselection likely for an agent.

Naming Consistency2/5

There are recognizable prefix families (app_, proj_, lib_, pcb_, sch_, design_, audit_, sim_), but many tools use bare names (list_components, run_drc, run_erc, get_board_info, review_design, route_plan, tool_invoke). Snake_case is mostly preserved, but verb styles and prefixing conventions vary widely across the surface.

Tool Count1/5

480 tools is far beyond a well-scoped set; even a large EDA automation suite does not justify this count for an agent-facing MCP server. tool_catalog and tool_invoke mitigate discovery, but the raw surface remains extreme and impractical to load or reason about reliably.

Completeness5/5

The surface covers project and document lifecycle, schematic and PCB CRUD, library authoring, simulation, routing, auditing, BOM/export, UI driving, and meta-tooling. Despite redundancy, there is no obvious core gap for the stated Altium/EDA automation domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues