app
adom-desktop-altium-bridge
Public Unreviewedby barrett-land
Adom Desktop bridge for Altium Designer — build and edit file-based symbol/footprint/Integrated libraries with LCSC/JLCPCB part numbers attached, and generate batch outputs (STEP, PDF, Gerber, BOM, CPL) from an OutJob, by driving Altium via DelphiScript.
← Commit history
Publish 0.0.51
11 files changed
+224−15
README.md+1−1@@ -11,7 +11,7 @@ verbs. ```bash adom-wiki pkg install barrett-land/adom-desktop-altium-bridge # the skill, in the container-adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.50/altium-bridge-manifest.json"}'+adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.51/altium-bridge-manifest.json"}' adom-desktop --ai-thread "<your thread>" altium_ping '{}' ```
SKILL.md+1−1@@ -12,7 +12,7 @@ forwards work into the running GUI as DelphiScript via `X2.EXE`. ## Install ```bash-adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.50/altium-bridge-manifest.json"}'+adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.51/altium-bridge-manifest.json"}' adom-desktop --ai-thread "<your thread>" altium_ping '{}' # -> {"altiumInstalled": true, "version": "..."} ```
altium-bridge-manifest.json+3−3@@ -1,3 +1,3 @@-{ "manifest_version": 1, "name": "altium", "version": "0.0.50",- "url": "https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.50/altium-bridge-v0.0.50.zip",- "sha256": "529217a4b61861ca2ba4ff5d825f78d639d68b93ff426951e935ce3d79a1cf64", "size": 42781, "released_at": "2026-08-12T17:45:50Z" }+{ "manifest_version": 1, "name": "altium", "version": "0.0.51",+ "url": "https://wiki.adom.inc/download/barrett-land/adom-desktop-altium-bridge/0.0.51/altium-bridge-v0.0.51.zip",+ "sha256": "0b67dbac6c1f2b8456693f982675eece2ba04c67c794efd5ebfb815b9fad800b", "size": 44940, "released_at": "2026-08-12T20:33:24Z" }
altium-bridge-v0.0.51.zipadded⋯ 1 unchanged line ⋯
bridge-src/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.0.50+0.0.51
bridge-src/__pycache__/server.cpython-312.pyc⋯ 1 unchanged line ⋯
bridge-src/addin/AdomAltium/AdomAltium.pas+187@@ -1950,6 +1950,192 @@ begin end; { ---- shared command dispatch. Assumes the global Req is loaded and GReqId set. ---- }+{ ---- command: remap an imported footprint's LAYERS onto the house convention and+ reset stroke widths.++ Why this exists: Altium's KiCad importer does not map KiCad's layers onto+ Altium's. It creates NEW mechanical layers carrying KiCad's own names.+ Measured on an LQFP-48 and a SOT-223: silkscreen does land correctly on+ Top Overlay, but the courtyard lands on "F.CrtYd" and the fab outline on+ "F.Fab" rather than "Courtyard Top" / "Assembly Top". Nothing downstream that+ reads the house layers -- OutJobs, assembly drawings, DRC -- then sees them,+ so the footprint looks right on screen and is wrong everywhere else.++ The importer also drops every (stroke (width ...)) it reads, logging+ "Unknown child stroke of fp_line", and defaults each track to 10 mil. KiCad's+ 0.12 mm silk and 0.05 mm courtyard therefore both arrive at 0.254 mm.++ remap : "SrcName|DstName;SrcName|DstName" matched BY LAYER NAME+ widths : "LayerName|microns;LayerName|microns" applied AFTER the remap+ footprint: a name, or '' / '*' for every footprint in the library.++ Widths are MICRONS so they stay integers (50 = 0.05 mm courtyard,+ 127 = 5 mil silk, which is what the hand-built libraries here use).++ Layers are resolved by probing integer ids against Board.LayerName[] rather+ than naming eMechanicalNN constants: an undeclared identifier is a COMPILE+ error, which takes the whole DelphiScript engine down instead of failing this+ one command. ---- }+procedure CmdRemapFootprintLayers;+var+ PcbPath, FpName, RemapSpec, WidthSpec, SavedStr : String;+ SD : IServerDocument;+ PcbLib : IPCB_Library;+ Board : IPCB_Board;+ Fp : IPCB_LibComponent;+ It : IPCB_GroupIterator;+ Prim : IPCB_Primitive;+ layers : TStringList; { "<name>=<id>" }+ remaps : TStringList; { "<srcname>=<dstname>" }+ widths : TStringList; { "<layername>=<microns>" }+ i, li, dstId, moved, widthed, fpCount : Integer;+ nm, s, item, a, b : String;+ pos1, pos2 : Integer;+ SavedOK : Boolean;+begin+ PcbPath := Req.Values['pcblib'];+ FpName := Req.Values['footprint'];+ RemapSpec := Req.Values['remap'];+ WidthSpec := Req.Values['widths'];+ if RemapSpec = '' then RemapSpec := 'F.CrtYd|Courtyard Top;F.Fab|Assembly Top';+ if WidthSpec = '' then WidthSpec := 'Top Overlay|127;Courtyard Top|50;Assembly Top|127';+ if PcbPath = '' then begin RespErr('missing pcblib'); Exit; end;++ GStage := 'open pcblib';+ SD := Client.OpenDocument('PCBLIB', PcbPath);+ if SD <> nil then Client.ShowDocument(SD);+ LastServerDoc := SD;+ if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;+ PcbLib := PCBServer.GetCurrentPCBLibrary;+ if PcbLib = nil then begin RespErr('could not open: ' + PcbPath); Exit; end;+ Board := PcbLib.Board;+ if Board = nil then begin RespErr('no board on the library'); Exit; end;++ layers := TStringList.Create;+ remaps := TStringList.Create;+ widths := TStringList.Create;+ moved := 0; widthed := 0; fpCount := 0;+ try+ GStage := 'enumerate layers';+ for li := 1 to 100 do+ begin+ nm := '';+ try+ nm := Board.LayerName[li];+ except+ nm := '';+ end;+ if nm <> '' then layers.Add(nm + '=' + IntToStr(li));+ end;++ GStage := 'parse specs';+ s := RemapSpec;+ while s <> '' do+ begin+ pos1 := Pos(';', s);+ if pos1 > 0 then begin item := Copy(s, 1, pos1 - 1); Delete(s, 1, pos1); end+ else begin item := s; s := ''; end;+ pos2 := Pos('|', item);+ if pos2 > 0 then+ begin+ a := Trim(Copy(item, 1, pos2 - 1));+ b := Trim(Copy(item, pos2 + 1, Length(item)));+ if (a <> '') and (b <> '') then remaps.Add(a + '=' + b);+ end;+ end;+ s := WidthSpec;+ while s <> '' do+ begin+ pos1 := Pos(';', s);+ if pos1 > 0 then begin item := Copy(s, 1, pos1 - 1); Delete(s, 1, pos1); end+ else begin item := s; s := ''; end;+ pos2 := Pos('|', item);+ if pos2 > 0 then+ begin+ a := Trim(Copy(item, 1, pos2 - 1));+ b := Trim(Copy(item, pos2 + 1, Length(item)));+ if (a <> '') and (b <> '') then widths.Add(a + '=' + b);+ end;+ end;++ GStage := 'remap';+ PCBServer.PreProcess;+ for i := 0 to PcbLib.ComponentCount - 1 do+ begin+ Fp := PcbLib.GetComponent(i);+ if Fp <> nil then+ if (FpName = '') or (FpName = '*') or (Fp.Name = FpName) then+ begin+ Inc(fpCount);+ It := Fp.GroupIterator_Create;+ It.SetState_FilterAll;+ try+ Prim := It.FirstPCBObject;+ while Prim <> nil do+ begin+ nm := '';+ try nm := Board.LayerName[Prim.Layer]; except nm := ''; end;++ { move to the house layer, if this one is mapped }+ if (nm <> '') and (remaps.Values[nm] <> '') then+ begin+ dstId := StrToIntDef(layers.Values[remaps.Values[nm]], -1);+ if dstId > 0 then+ begin+ Prim.Layer := dstId;+ Inc(moved);+ try nm := Board.LayerName[Prim.Layer]; except end;+ end;+ end;++ { reset the stroke width for whatever layer it now sits on.+ Tracks expose Width, arcs expose LineWidth, and pads/regions+ expose neither -- so try in turn rather than switch on an+ ObjectId constant. }+ if (nm <> '') and (widths.Values[nm] <> '') then+ begin+ li := StrToIntDef(widths.Values[nm], 0);+ if li > 0 then+ begin+ try+ Prim.Width := MilsToCoord(li / 25.4);+ Inc(widthed);+ except+ try+ Prim.LineWidth := MilsToCoord(li / 25.4);+ Inc(widthed);+ except+ end;+ end;+ end;+ end;++ Prim := It.NextPCBObject;+ end;+ finally+ Fp.GroupIterator_Destroy(It);+ end;+ end;+ end;+ PCBServer.PostProcess;+ finally+ layers.Free;+ remaps.Free;+ widths.Free;+ end;++ GStage := 'save';+ Board.ViewManager_FullUpdate;+ SavedOK := False;+ if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');+ if SavedOK then SavedStr := 'true' else SavedStr := 'false';+ RespOk(', "footprints": ' + IntToStr(fpCount) ++ ', "movedPrimitives": ' + IntToStr(moved) ++ ', "widthsSet": ' + IntToStr(widthed) ++ ', "saved": ' + SavedStr);+end;++ procedure Dispatch; var Cmd : String; begin@@ -1981,6 +2167,7 @@ begin else if Cmd = 'add_body' then CmdAddBody else if Cmd = 'set_visibility' then CmdSetVisibility else if Cmd = 'set_text_style' then CmdSetTextStyle+ else if Cmd = 'remap_fp_layers' then CmdRemapFootprintLayers else if Cmd = 'export_step' then CmdExportStep else if Cmd = 'run_outjob' then CmdRunOutJob else RespErr('unknown command: ' + Cmd);
bridge-src/bridge.json+2−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Bridge",- "version": "0.0.50",+ "version": "0.0.51", "description": "Drives Altium Designer from a container: build, clone and edit file-based symbol/footprint/Integrated libraries with LCSC/JLCPCB part numbers attached, and generate batch outputs (STEP, PDF, Gerber, BOM, CPL) from an OutJob. Pairs with the container-side altium-jlc orchestrator (adom-jlcpcb + chip-fetcher).", "homepage": "https://wiki.adom.inc/adom/adom-desktop-altium-bridge", "author": "Adom Inc.",@@ -44,6 +44,7 @@ "altium_add_body", "altium_set_visibility", "altium_set_text_style",+ "altium_remap_fp_layers", "altium_copy_sch_component", "altium_copy_footprint", "altium_script_state",
bridge-src/server.py+17@@ -177,6 +177,22 @@ def handle_set_visibility(args: dict) -> dict: timeout=args.get("timeout", 90)) +def handle_remap_fp_layers(args: dict) -> dict:+ """Remap an imported footprint's layers onto the house convention and reset+ stroke widths. Altium's KiCad importer does not map KiCad layers onto Altium's:+ silkscreen does land on Top Overlay, but the courtyard lands on 'F.CrtYd' and the+ fab outline on 'F.Fab' instead of 'Courtyard Top' / 'Assembly Top', so OutJobs,+ assembly drawings and DRC never see them. It also drops every stroke width, so+ KiCad's 0.12mm silk and 0.05mm courtyard both arrive at Altium's 10 mil default.++ remap = "SrcName|DstName;..." matched by layer NAME+ widths = "LayerName|microns;..." applied after the remap (microns keep it integral)+ footprint = a name, or '' / '*' for the whole library."""+ keys = ("pcblib", "footprint", "remap", "widths")+ return addin_call("remap_fp_layers", {k: args.get(k) for k in keys},+ timeout=args.get("timeout", 240))++ def handle_set_text_style(args: dict) -> dict: """Normalise font + colour on a component's designator, comment and parameters. Defaults are Altium's own: FontID 1 (Times New Roman 10) and colour 8388608,@@ -461,6 +477,7 @@ COMMAND_HANDLERS = { "altium_add_body": handle_add_body, "altium_set_visibility": handle_set_visibility, "altium_set_text_style": handle_set_text_style,+ "altium_remap_fp_layers": handle_remap_fp_layers, "altium_copy_sch_component": handle_copy_sch_component, "altium_copy_footprint": handle_copy_footprint, }
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-altium-bridge",- "version": "0.0.50",+ "version": "0.0.51", "type": "app", "description": "Adom Desktop bridge for Altium Designer: file-based symbol/footprint/IntLib authoring with LCSC/JLCPCB part numbers, Gerber/BOM/CPL batch output, and an in-process .NET extension adding parameterized headless verbs - STEP export with hole and copper options, and KiCad import with no wizard.", "tags": [
page.json+11−7@@ -1,8 +1,8 @@ { "slug": "adom-desktop-altium-bridge",- "version": "0.0.50",+ "version": "0.0.51", "type": "app",- "description": "Adom Desktop bridge for Altium Designer \u2014 build and edit file-based symbol/footprint/Integrated libraries with LCSC/JLCPCB part numbers attached, and generate batch outputs (STEP, PDF, Gerber, BOM, CPL) from an OutJob, by driving Altium via DelphiScript.",+ "description": "Adom Desktop bridge for Altium Designer: file-based symbol/footprint/IntLib authoring with LCSC/JLCPCB part numbers, Gerber/BOM/CPL batch output, and an in-process .NET extension adding parameterized headless verbs - STEP export with hole and copper options, and KiCad import with no wizard.", "tags": [ "app", "adom-desktop",@@ -18,6 +18,7 @@ }, "needs_sudo": false, "hero": {+ "type": "image", "path": "docs/hero.png" }, "author": {@@ -25,13 +26,16 @@ "email": "[email protected]" }, "discovery_triggers": [- "add a part to my altium library", "drive altium from claude", "altium bridge",- "export step from altium",+ "export step from altium headless",+ "add a part to my altium library",+ "altium extension",+ "install altium extension",+ "altium step export", "run an altium outjob",- "build an altium footprint",- "altium symbol library"+ "import kicad into altium",+ "altium kicad importer" ], "discovery_pitch": "Use when the user wants Claude to author or edit Altium libraries, or to generate STEP/PDF/Gerber/BOM/CPL from an Altium board on their Windows machine.", "sample_prompts": [@@ -48,4 +52,4 @@ "prompt": "Is the Altium bridge connected and what version is it?" } ]-}+}