app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
← Commit history
0.2.15: add_symbol_art clear + line-segment mode; symbol_prims models gate
6 files changed
+95−32
bridge-rs/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "altium-bridge"-version = "0.2.14"+version = "0.2.15" edition = "2021" description = "Adom altium-bridge — the bridge process, in Rust. Calls ab's verbs for the desktop; speaks file IPC to the resident DelphiScript agent inside Altium." license = "MIT"
bridge-rs/src/library.rs+3−3@@ -342,11 +342,11 @@ pub fn add_symbol_art(args: &Value) -> Value { let kr = format!("{:.0},{:.0},{:.0},{:.0}", rect.0 * MIL, rect.1 * MIL, rect.2 * MIL, rect.3 * MIL); let payload: String = polys.iter().map(|pl| pl.iter().map(|(x, y)| format!("{:.0},{:.0}", x * MIL, y * MIL)).collect::<Vec<_>>().join(" ")).collect::<Vec<_>>().join("\n"); support::agent_open(&lib, 60);- let mut r = agent::call("add_symbol_art", &[("libref", name.clone()), ("kicadRect", kr)], Some(&payload), timeout_of(args, 180));+ let mut r = agent::call("add_symbol_art", &[("libref", name.clone()), ("kicadRect", kr), ("mode", s_or(args, "mode", "lines")), ("clear", if args.get("clear").and_then(|b| b.as_bool()) == Some(false) { "0".into() } else { "1".into() })], Some(&payload), timeout_of(args, 240)); r["_hints"] = json!([ format!("{} polylines drawn into {name} from the KiCad symbol's baked outline; altium_save {{filePath}} the SchLib, then altium_show_symbol to check the chip sits between the pin-name columns.", polys.len()), "The outline is the reviewed chip artwork (side-etched MPN for optical tops); do not re-etch or re-generate it here, reuse the wiki page's artwork cache.",- "This verb APPENDS: running it twice draws the outline twice. remove_symbol + rebuild, or clone the part first, to redo it."]);+ "Replaces earlier art by default (clear:true removes the component's polylines and lines first); pass clear:false to stack. mode 'lines' (default) draws eLine segments, mode 'polyline' draws ISch_Polyline objects."]); r } @@ -357,7 +357,7 @@ pub fn symbol_prims(args: &Value) -> Value { _ => return json!({"success": false, "error": "libraryPath (.SchLib) required"}), }; support::agent_open(&lib, 60);- agent::call("symbol_prims", &[("libref", s_or(args, "symbolName", ""))], None, timeout_of(args, 60))+ agent::call("symbol_prims", &[("libref", s_or(args, "symbolName", "")), ("models", if args.get("models").and_then(|b| b.as_bool()) == Some(true) { "1".into() } else { "0".into() })], None, timeout_of(args, 60)) } /// Replace every footprint model on a SchLib component with one PCBLIB model.
bridge-rs/src/main.rs+1−1@@ -25,7 +25,7 @@ mod win32; use std::time::Instant; use tiny_http::{Header, Method, Response, Server}; -pub const BRIDGE_VERSION: &str = "0.2.14";+pub const BRIDGE_VERSION: &str = "0.2.15"; fn json_response(status: u16, body: &serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> { let data = serde_json::to_vec(body).unwrap_or_else(|_| b"{}".to_vec());
bridge-rs/src/verbs.rs+3−3@@ -108,16 +108,16 @@ fn describe() -> Value { &["place_on_sch", "show_3d_board", "add_body"], &["assigns/stubs apply to the LAST part placed.", "pcb is empty when pcbPath is omitted: the board exists only unsaved as PCB<n>.PcbDoc."], json!({"libraryPath": "C:/lib/Adom_ToF.PcbLib", "footprintName": "VL53L5CX;VL53L7CX;VL53L8CX", "wMm": 39, "hMm": 14, "pcbPath": "C:/proj/tof.PcbDoc"})), verb_doc("symbol_prims", "Read a SchLib component's drawing: primitive counts by kind, body rectangle, polyline bounding box, display mode and the footprint models. Read-only, use it to verify add_symbol_art / set_models.",- json!({"libraryPath": "path (.SchLib)", "symbolName": "string"}), json!({"pins": "int", "rectangles": "int", "polylines": "int", "bodyRectMil": "[..]", "polyBboxMil": "[..]", "models": "[\"PCBLIB:NAME\"]"}), 60,+ json!({"libraryPath": "path (.SchLib)", "symbolName": "string", "models": "bool, default false (the model walk halts the engine on some builds)"}), json!({"pins": "int", "rectangles": "int", "lines": "int", "polylines": "int", "bodyRectMil": "[..]", "polyBboxMil": "[..]", "models": "[\"PCBLIB:NAME\"]"}), 60, "polylines should equal what add_symbol_art reported and polyBboxMil should sit inside bodyRectMil.", &["add_symbol_art", "set_models", "read_symbol"], &[], json!({})), verb_doc("set_models", "Replace every footprint model on a SchLib component with ONE PCBLIB model (set_params footprint and add_models APPEND, which is how a symbol ends up with two).", json!({"libraryPath": "path (.SchLib)", "symbolName": "string", "footprint": "string (PcbLib footprint name)"}), json!({"removed": "int", "footprint": "string"}), 60, "altium_save the SchLib afterwards.", &["symbol_prims", "set_params", "add_models"], &["Removes every existing model, including 3D-only ones."], json!({})), verb_doc("add_symbol_art", "Draw the chip-outline artwork (the chip-thumbnailer polylines adom-symbol baked into the KiCad symbol) into a SchLib component, fitted to its body rectangle, so the Altium symbol shows the same chip as KiCad and Fusion.",- json!({"libraryPath": "path (.SchLib)", "symbolName": "string (LibReference)", "kicadSym": "Windows path to the .kicad_sym that carries the baked outline (stage it with send_files)"}),+ json!({"libraryPath": "path (.SchLib)", "symbolName": "string (LibReference)", "kicadSym": "Windows path to the .kicad_sym that carries the baked outline (stage it with send_files)", "clear": "bool, default true: remove the component's existing polylines/lines first", "mode": "lines (default) | polyline"}), json!({"polylines": "int", "points": "int", "bodyRectMil": "[x0,y0,x1,y1]"}), 180, "Run once per part right after assemble-altium / build_symbol_modes, then altium_save the SchLib and altium_show_symbol to check.",- &["show_symbol", "read_symbol", "save"], &["APPENDS on every call: run it once per symbol.", "The KiCad symbol must already carry the outline (adom-symbol create bakes it; --outline-name none for side-etched chips)."],+ &["show_symbol", "symbol_prims", "save"], &["clear:true (default) also removes hand-drawn lines on the symbol; only the pins and the body rectangle survive.", "The KiCad symbol must already carry the outline (adom-symbol create bakes it; --outline-name none for side-etched chips)."], json!({"libraryPath": "C:/lib/Adom_ToF.SchLib", "symbolName": "VL53L5CX", "kicadSym": "C:/lib/VL53L5CX.kicad_sym"})), verb_doc("place_on_sch", "Place one symbol, or a ';' list of symbols, from the open SchLib on ONE new sheet (sized A4 or B for the part count, parts inside the frame as U1..Un with the part number as Comment); fills the title block from title/number/revision/drawnBy and places description as a text frame; saves to schPath when given.", json!({"libraryPath": "path (.SchLib)", "symbolName": "string | 'A;B;C'", "schPath": "path (.SchDoc, optional save)", "title": "string (Title)", "number": "string (DocumentNumber)", "revision": "string", "drawnBy": "string", "description": "string (what the sheet shows, 2-3 sentences)"}),
page.json+1−1@@ -139,4 +139,4 @@ "skills/altium-wiki-import/SKILL.md", "uninstall.sh" ]-}+}
src/agent/AdomAgent.pas.tmpl+86−23@@ -1401,13 +1401,22 @@ Var Comp : ISch_Component; Rect : ISch_Rectangle; Poly : ISch_Polyline;+ Seg : ISch_Line;+ Prim : ISch_GraphicalObject;+ Victims : TInterfaceList; PL, Pts : TStringList;- I, J, N, NPoly, NPts : Integer;- Name, KR, Ln, Tok : String;- KX0, KY0, KX1, KY1, AX0, AY0, AX1, AY1, SX, SY, MX, MY, Tmp : Double;+ I, J, N, NPoly, NPts, NCleared : Integer;+ Name, KR, Ln, Tok, Mode : String;+ KX0, KY0, KX1, KY1, AX0, AY0, AX1, AY1, SX, SY, MX, MY, PX, PY, Tmp : Double;+ UseLines : Boolean; Begin Name := Param(Lines, 'libref'); KR := Param(Lines, 'kicadRect');+ Mode := Param(Lines, 'mode');+ { lines (default): every polyline becomes N-1 eLine segments, the primitive BuildSymbolModes has+ always drawn with; polyline: ISch_Polyline objects (measured 2026-09-19: they render on one+ part and stay invisible on another with identical geometry). }+ UseLines := (Mode <> 'polyline'); SLib := SchServer.GetCurrentSchDocument; If (SLib = Nil) Or (SLib.ObjectID <> eSchLib) Then Begin@@ -1457,10 +1466,34 @@ Begin End; SX := (AX1 - AX0) / (KX1 - KX0); SY := (AY1 - AY0) / (KY1 - KY0);+ SchServer.ProcessControl.PreProcess(SLib, '');+ { replace, never stack: every earlier polyline and every line that is not part of the pins+ or the body outline is art from a previous run. Collect first, remove after the walk. }+ NCleared := 0;+ If Param(Lines, 'clear') <> '0' Then+ Begin+ Victims := TInterfaceList.Create;+ Iter := Comp.SchIterator_Create;+ Iter.AddFilter_ObjectSet(MkSet(ePolyline, eLine));+ Prim := Iter.FirstSchObject;+ While Prim <> Nil Do+ Begin+ Victims.Add(Prim);+ Prim := Iter.NextSchObject;+ End;+ Comp.SchIterator_Destroy(Iter);+ For I := 0 To Victims.Count - 1 Do+ Begin+ Prim := Victims[I];+ Comp.RemoveSchObject(Prim);+ SchServer.DestroySchObject(Prim);+ NCleared := NCleared + 1;+ End;+ Victims.Free;+ End; PL := TStringList.Create; PL.Text := Payload; NPoly := 0; NPts := 0;- SchServer.ProcessControl.PreProcess(SLib, ''); For I := 0 To PL.Count - 1 Do Begin Ln := Trim(PL[I]);@@ -1469,21 +1502,47 @@ Begin N := Pts.Count; If N >= 2 Then Begin- Poly := SchServer.SchObjectFactory(ePolyline, eCreate_Default);- Poly.OwnerPartId := 1;- Poly.OwnerPartDisplayMode := 0;- Poly.LineWidth := eSmall;- Poly.Color := $8A8A8A;- Poly.VerticesCount := N;- For J := 1 To N Do+ If UseLines Then+ Begin+ PX := 0; PY := 0;+ For J := 1 To N Do+ Begin+ Tok := Pts[J - 1];+ MX := AX0 + (StrToFloat(Field(Tok, 1)) - KX0) * SX;+ MY := AY0 + (StrToFloat(Field(Tok, 2)) - KY0) * SY;+ If J > 1 Then+ Begin+ Seg := SchServer.SchObjectFactory(eLine, eCreate_Default);+ Seg.OwnerPartId := 1;+ Seg.OwnerPartDisplayMode := 0;+ Seg.LineWidth := eSmall;+ Seg.Color := $8A8A8A;+ Seg.Location := Point(MilsToCoord(PX), MilsToCoord(PY));+ Seg.Corner := Point(MilsToCoord(MX), MilsToCoord(MY));+ Comp.AddSchObject(Seg);+ SchServer.RobotManager.SendMessage(Comp.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, Seg.I_ObjectAddress);+ End;+ PX := MX; PY := MY;+ End;+ End+ Else Begin- Tok := Pts[J - 1];- MX := AX0 + (StrToFloat(Field(Tok, 1)) - KX0) * SX;- MY := AY0 + (StrToFloat(Field(Tok, 2)) - KY0) * SY;- Poly.Vertex[J] := Point(MilsToCoord(MX), MilsToCoord(MY));+ Poly := SchServer.SchObjectFactory(ePolyline, eCreate_Default);+ Poly.OwnerPartId := 1;+ Poly.OwnerPartDisplayMode := 0;+ Poly.LineWidth := eSmall;+ Poly.Color := $8A8A8A;+ Poly.VerticesCount := N;+ For J := 1 To N Do+ Begin+ Tok := Pts[J - 1];+ MX := AX0 + (StrToFloat(Field(Tok, 1)) - KX0) * SX;+ MY := AY0 + (StrToFloat(Field(Tok, 2)) - KY0) * SY;+ Poly.Vertex[J] := Point(MilsToCoord(MX), MilsToCoord(MY));+ End;+ Comp.AddSchObject(Poly);+ SchServer.RobotManager.SendMessage(Comp.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, Poly.I_ObjectAddress); End;- Comp.AddSchObject(Poly);- SchServer.RobotManager.SendMessage(Comp.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, Poly.I_ObjectAddress); NPoly := NPoly + 1; NPts := NPts + N; End;@@ -1493,7 +1552,8 @@ Begin PL.Free; SLib.GraphicallyInvalidate; Result := '{' + JRaw('ok', 'true') + ',' + JStr('libref', Comp.LibReference) + ',' + JRaw('polylines', IntToStr(NPoly)) + ',' +- JRaw('points', IntToStr(NPts)) + ',' + JRaw('bodyRectMil', '[' + IntToStr(Round(AX0)) + ',' + IntToStr(Round(AY0)) + ',' + IntToStr(Round(AX1)) + ',' + IntToStr(Round(AY1)) + ']') + '}';+ JRaw('points', IntToStr(NPts)) + ',' + JRaw('cleared', IntToStr(NCleared)) + ',' + JStr('mode', Mode) + ',' ++ JRaw('bodyRectMil', '[' + IntToStr(Round(AX0)) + ',' + IntToStr(Round(AY0)) + ',' + IntToStr(Round(AX1)) + ',' + IntToStr(Round(AY1)) + ']') + '}'; End; { Read a SchLib component drawing: primitive counts by kind, the body rectangle, the polyline@@ -1576,17 +1636,20 @@ Begin End; Comp.SchIterator_Destroy(Iter); Models := '';- For I := 0 To Comp.ImplementationCount - 1 Do+ If Param(Lines, 'models') = '1' Then Begin- Impl := Comp.Implementation[I];- If Models <> '' Then Models := Models + ',';- Models := Models + '"' + Impl.ModelType + ':' + Impl.ModelName + '"';+ For I := 0 To Comp.ImplementationCount - 1 Do+ Begin+ Impl := Comp.Implementation[I];+ If Models <> '' Then Models := Models + ',';+ Models := Models + '"' + Impl.ModelType + ':' + Impl.ModelName + '"';+ End; End; Result := '{' + JRaw('ok', 'true') + ',' + JStr('libref', Comp.LibReference) + ',' + JRaw('pins', IntToStr(NPin)) + ',' + JRaw('rectangles', IntToStr(NRect)) + ',' + JRaw('lines', IntToStr(NLine)) + ',' + JRaw('polylines', IntToStr(NPoly)) + ',' + JRaw('other', IntToStr(NOther)) + ',' + JRaw('displayMode', IntToStr(Comp.DisplayMode)) + ',' + JRaw('displayModeCount', IntToStr(Comp.DisplayModeCount)) + ',' +- JRaw('currentPartId', IntToStr(Comp.CurrentPartID)) + ',' + JRaw('partCount', IntToStr(Comp.PartCount)) + ',' ++ JRaw('currentPartId', IntToStr(Comp.CurrentPartID)) + ',' + JRaw('bodyRectMil', '[' + IntToStr(RX0) + ',' + IntToStr(RY0) + ',' + IntToStr(RX1) + ',' + IntToStr(RY1) + ']') + ',' + JRaw('polyBboxMil', '[' + IntToStr(PX0) + ',' + IntToStr(PY0) + ',' + IntToStr(PX1) + ',' + IntToStr(PY1) + ']') + ',' + JRaw('models', '[' + Models + ']') + '}';